Full Code of shoumma/ReForum for AI

master f4b209b579f1 cached
126 files
634.6 KB
203.1k tokens
1433 symbols
1 requests
Download .txt
Showing preview only (677K chars total). Download the full file or copy to clipboard to get everything.
Repository: shoumma/ReForum
Branch: master
Commit: f4b209b579f1
Files: 126
Total size: 634.6 KB

Directory structure:
gitextract_bfqsak_0/

├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── backend/
│   ├── dev.js
│   ├── entities/
│   │   ├── admin/
│   │   │   ├── api.js
│   │   │   └── controller.js
│   │   ├── discussion/
│   │   │   ├── api.js
│   │   │   ├── controller.js
│   │   │   └── model.js
│   │   ├── forum/
│   │   │   ├── api.js
│   │   │   ├── controller.js
│   │   │   └── model.js
│   │   ├── opinion/
│   │   │   ├── api.js
│   │   │   ├── controller.js
│   │   │   └── model.js
│   │   └── user/
│   │       ├── api.js
│   │       ├── controller.js
│   │       └── model.js
│   ├── express.js
│   ├── mockData/
│   │   ├── discussions.js
│   │   ├── forum.js
│   │   ├── opinions.js
│   │   └── users.js
│   ├── passport.js
│   ├── routes.js
│   └── utilities/
│       └── tools.js
├── config/
│   ├── credentials.js
│   ├── serverConfig.js
│   ├── webpack.dev.config.js
│   └── webpack.prod.config.js
├── docs/
│   ├── api.md
│   └── system_overview.md
├── frontend/
│   ├── App/
│   │   ├── Admin.js
│   │   ├── App.js
│   │   ├── actions.js
│   │   ├── api.js
│   │   ├── constants.js
│   │   ├── index.js
│   │   ├── reducers.js
│   │   ├── store.js
│   │   └── styles.css
│   ├── Components/
│   │   ├── Button/
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   ├── Dashboard/
│   │   │   ├── Counts/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   └── ForumBox/
│   │   │       ├── index.js
│   │   │       └── styles.css
│   │   ├── FeedBox/
│   │   │   ├── DiscussionBox/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   ├── Footer/
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   ├── Header/
│   │   │   ├── Logo/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   ├── NavigationBar/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   └── UserMenu/
│   │   │       ├── index.js
│   │   │       └── styles.css
│   │   ├── NewDiscussion/
│   │   │   ├── PinButton/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   └── TagsInput/
│   │   │       ├── index.js
│   │   │       └── styles.css
│   │   ├── RichEditor/
│   │   │   ├── BlockStyleControls.js
│   │   │   ├── InlineStyleControls.js
│   │   │   ├── StyleButton.js
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   ├── SideBar/
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   ├── SingleDiscussion/
│   │   │   ├── Discussion/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   ├── Opinion/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   └── ReplyBox/
│   │   │       ├── index.js
│   │   │       └── styles.css
│   │   ├── Tag/
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   └── UserProfile/
│   │       └── Profile/
│   │           ├── index.js
│   │           └── styles.css
│   ├── Containers/
│   │   ├── AdminHeader/
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   └── Header/
│   │       ├── index.js
│   │       └── styles.css
│   ├── SharedStyles/
│   │   ├── appLayout.css
│   │   └── globalStyles.css
│   └── Views/
│       ├── AdminDashboard/
│       │   ├── actions.js
│       │   ├── api.js
│       │   ├── constants.js
│       │   ├── index.js
│       │   ├── reducers.js
│       │   └── styles.css
│       ├── ForumFeed/
│       │   ├── actions.js
│       │   ├── api.js
│       │   ├── constants.js
│       │   ├── index.js
│       │   ├── reducers.js
│       │   ├── styles.css
│       │   └── tests/
│       │       └── actions.test.js
│       ├── NewDiscussion/
│       │   ├── actions.js
│       │   ├── api.js
│       │   ├── constants.js
│       │   ├── index.js
│       │   ├── reducers.js
│       │   └── styles.css
│       ├── NotFound/
│       │   └── index.js
│       ├── SingleDiscussion/
│       │   ├── actions.js
│       │   ├── api.js
│       │   ├── constants.js
│       │   ├── index.js
│       │   ├── reducers.js
│       │   └── styles.css
│       └── UserProfile/
│           ├── actions.js
│           ├── api.js
│           ├── constants.js
│           ├── index.js
│           ├── reducers.js
│           └── styles.css
├── package.json
├── public/
│   ├── build/
│   │   ├── bundle.js
│   │   └── style.css
│   └── index.html
└── server.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .babelrc
================================================
{
  "presets": [
    "es2015", "stage-2", "react"
  ]
}


================================================
FILE: .editorconfig
================================================
[*]
indent_style = space
end_of_line = lf
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true

[*.md]
max_line_length = 0
trim_trailing_whitespace = false


================================================
FILE: .eslintrc
================================================
{
  "ecmaFeatures": {
    "jsx": true,
    "modules": true
  },
  "env": {
    "browser": true,
    "node": true
  },
  "parser": "babel-eslint",
  "rules": {
    "comma-dangle": ["error", "always-multiline"],
    "semi": ["error", "always"],
    "quotes": [2, "single"],
    "strict": [2, "never"],
    "react/jsx-uses-react": 2,
    "react/jsx-uses-vars": 2,
    "react/react-in-jsx-scope": 2
  },
  "plugins": [ "react" ]
}


================================================
FILE: .gitignore
================================================
# dependency
node_modules

# yarn
yarn.lock

# npm cache
.npm

# Logs
logs
*.log
npm-debug.log*

# coverages
coverage

# OSX stuffs
.DS_Store


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2016 Provash Shoumma

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
![logo](./docs/design_assets/logo.png)


# ReForum
A minimal forum application built with the following technologies:
* [React](https://facebook.github.io/react/)
* [Redux](http://redux.js.org/)
* [Webpack](https://webpack.js.org/)
* [ExpressJS](https://expressjs.com/)
* [PassportJS](http://passportjs.org/)
* [MongoDB](https://www.mongodb.com/)

### Application Features
* Users can post a discussion
* Users can reply their opinions regarding discussion
* Users can favorite discussions
* Users have their own profile page
* Admin can create new forum categories
* Admin have a lot of power over every users discussions and opinions :-p

### Documentations
* [API Docs](https://github.com/shoumma/ReForum/blob/master/docs/api.md)
* [System Overview](https://github.com/shoumma/ReForum/blob/master/docs/system_overview.md)

### Home View
![home view](./docs/design_assets/home_view.jpg)

### Admin View
![admin view](./docs/design_assets/admin_view.jpg)

## Deploy on you own server

Please make sure you have following software installed in your system:
* Node.js > 6.0
* NPM / Yarn
* Git
* MongoDB

First we need to clone the repository:
```
$ git clone https://github.com/shoumma/ReForum
```

Then we have to install the necessary dependencies using either NPM or Yarn:
```
$ npm i
```
```
$ yarn
```

Since the app currently uses GitHub authentication, we need to configure a GitHub OAuth application. You can register a new application from this link https://github.com/settings/developers

We need to grab the following information from the OAuth application.
* Client ID
* Client Secret
* Callback URL

The `Callback URL` is the domain where GitHub will redirect the user after a successful login. You can use a domain name or local host. But we need to append the URL with the path `/api/user/authViaGitHub/callback`. So, the complete url will look like:
`https://localhost:8080/api/user/authViaGitHub/callback`

Now, we need to configure the credentials inside of the codebase. Open the file `config/credentials.js` add the necessary information. The file looks like this:
```js
module.exports = {
  GITHUB_CLIENT_ID: '',
  GITHUB_CLIENT_SECRET: '',
  GITHUB_CALLBACK_URL: '',
  DBURL: '',
};
```

We need to provide all the information here. You can notice that we need the database url here too. My `local` MongoDB url looks like:
```
mongodb://localhost:27017/reforum
```

Now we are ready to run the application. You can run either run the development environment of the application which will include Hot-Reload for JS codes using Webpack and the Redux dev tool extension, or you can run the production edition. The default port for developer edition is `8080`, and for production is `process.env.PORT`.

To run the app in development environment:
```
$ npm run start:dev
```

To run the app in production environment:
```
$ npm run start
```

Now, if you visit [http://localhost:8080](http://localhost:8080) (if you ran the dev), or the production URL, you will see that the application is up and running. Congratulation! But, wait a minute, it's showing you `Sorry, couldn't find the forum`. That is because, we didn't create any forum yet. You can now sign up via github and then visit the admin panel with the url [http://localhost:8080/admin](http://localhost:8080/admin). The application is currently configured in a way that, the first user will become the admin for the system.

Here we can create new forums and that forum will be displayed in the application. The first forum will be used as default forum.

Congratulation! You now have a clone of this application in your server. :-)

## Path for Future Work
* Add search functionality
* Add unit tests for both backend and frontend
* Ability to change the name and logo of the site from admin panel.
* Make the installation process more interactive
* Add multiple theme support.

## License
[MIT License](https://github.com/shoumma/Mister-Poster/blob/master/LICENSE). Do whatever you want to do. :-)

## Conclusion
The application is created with lots of ♥. Any pull request, issues and contribution is very appreciated. It would be really great if we can take this application to the next level, where it can be used as a platform for forums.

[Provash Shoumma](https://twitter.com/proshoumma)


================================================
FILE: backend/dev.js
================================================
/**
 * module dependencies for development
 */
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');

/**
 * development configuration
 */
const devConfigs = (app) => {
  // webpack development configuration
  const webpackConfig = require('../config/webpack.dev.config');
  const webpackCompiler = webpack(webpackConfig);

  // apply dev middleware
  app.use(webpackDevMiddleware(webpackCompiler, {
    publicPath: webpackConfig.output.publicPath,
    hot: true,
    stats: true,
  }));

  // apply hot middleware
  app.use(webpackHotMiddleware(webpackCompiler));
};

module.exports = devConfigs;


================================================
FILE: backend/entities/admin/api.js
================================================
// controllers
const getAdminDashInfo = require('./controller').getAdminDashInfo;
const createForum = require('./controller').createForum;
const deleteForum = require('./controller').deleteForum;
const deleteUser = require('./controller').deleteUser;
const deleteDiscussion = require('./controller').deleteDiscussion;

/**
 * admin apis
 * @param  {Object} app
 */
const adminAPI = (app) => {
  // get all info for admin dashboard
  app.get('/api/admin/admin_dashboard_info', (req, res) => {
    if (req.user && req.user.role === 'admin') {
      getAdminDashInfo().then(
        (data) => { res.send(data); },
        (error) => { res.send(error); }
      );
    }
    else res.send({ error: 'You are not admin buddy 😛' });
  });

  // create a forum
  app.post('/api/admin/create_forum', (req, res) => {
    if (req.user && req.user.role === 'admin') {
      const {
        title,
        slug,
      } = req.body;

      createForum({ forum_name: title, forum_slug: slug }).then(
        (data) => { res.send(data); },
        (error) => { res.send(error); }
      );
    }
    else res.send({ error: 'You are not admin buddy 😛' });
  });

  // delete a forum
  app.post('/api/admin/delete_forum', (req, res) => {
    if (req.user && req.user.role === 'admin') {
      deleteForum(req.body).then(
        (data) => { res.send(data); },
        (error) => { res.send(error); }
      );
    }
    else res.send({ error: 'You are not admin buddy 😛' });
  });

  // delete an user
  app.post('/api/admin/delete_user', (req, res) => {
    if (req.user && req.user.role === 'admin') {
      deleteUser(req.body).then(
        (data) => { res.send(data); },
        (error) => { res.send(error); }
      );
    }
    else res.send({ error: 'You are not admin buddy 😛' });
  });

  // delete a discussion
  app.post('/api/admin/delete_discussion', (req, res) => {
    if (req.user && req.user.role === 'admin') {
      deleteDiscussion(req.body).then(
        (data) => { res.send(data); },
        (error) => { res.send(error); }
      );
    }
    else res.send({ error: 'You are not admin buddy 😛' });
  });
};

module.exports = adminAPI;


================================================
FILE: backend/entities/admin/controller.js
================================================
const waterfall = require('async/waterfall');

// models
const Discussion = require('../discussion/model');
const Opinion = require('../opinion/model');
const Forum = require('../forum/model');
const User = require('../user/model');

/**
 * get the information for admin dashboard
 * @return {Promise}
 */
const getAdminDashInfo = () => {
  return new Promise((resolve, reject) => {
    waterfall([
      (callback) => {
        Discussion.count().exec((error, count) => {
          callback(null, { discussionCount: count });
        });
      },
      (lastResult, callback) => {
        Opinion.count().exec((error, count) => {
          callback(null, Object.assign(lastResult, { opinionCount: count }));
        });
      },
      (lastResult, callback) => {
        Forum.count().exec((error, count) => {
          callback(null, Object.assign(lastResult, { forumCount: count }));
        });
      },
      (lastResult, callback) => {
        User.count().exec((error, count) => {
          callback(null, Object.assign(lastResult, { userCount: count }));
        });
      },
      (lastResult, callback) => {
        Forum
        .find({})
        .sort({ date: -1 })
        .lean()
        .exec((error, forums) => {
          callback(null, Object.assign(lastResult, { forums }));
        });
      },
    ], (error, result) => {
      if (error) { console.log(error); reject(error); }
      else resolve(result);
    });
  });
};

/**
 * create a new forum
 * @param  {String} forum_name
 * @param  {String} forum_slug
 * @return {Promise}
 */
const createForum = ({ forum_name, forum_slug }) => {
  return new Promise((resolve, reject) => {
    // check if the forum exists
    Forum
    .findOne({ forum_slug })
    .exec((error, forum) => {
      if (error) { console.log(error); reject({ serverError: true }); }
      else if (forum) { reject({ alreadyExists: true }); }
      else {
        // forum does not exists, so create a new one
        const newForum = new Forum({
          forum_slug,
          forum_name,
        });

        newForum.save((error) => {
          if (error) { console.log(error); reject({ created: false }); }
          else { resolve(Object.assign({}, newForum, { created: true })); }
        });
      }
    });
  });
};

/**
 * delete an entire forum
 * @param  {String} forum_id
 * @return {Promise}
 */
const deleteForum = ({ forum_id }) => {
  return new Promise((resolve, reject) => {
    // first remove any discussion regarding the forum
    Discussion.remove({ forum_id }).exec((error) => {
      if (error) { console.log(error); reject({ deleted: false }); }
      else {
        // remove any opinion regarding the forum
        Opinion.remove({ forum_id }).exec((error) => {
          if (error) { console.log(error); reject({ deleted: false }); }
          else {
            // now we can remove the forum
            Forum.remove({ _id: forum_id }).exec((error) => {
              if (error) { console.log(error); reject({ deleted: false }); }
              else { resolve({ deleted: true }); }
            });
          }
        });
      }
    });
  });
};

/**
 * delete an user
 * @param  {String} user_id
 * @return {Promise}
 */
const deleteUser = ({ user_id }) => {
  return new Promise((resolve, reject) => {
    // first we need to remvoe any discussion the user created
    Discussion.remove({ user_id }).exec((error) => {
      if (error) { console.log(error); reject({ deleted: false }); }
      else {
        // now we need to remove any opinions that are created by the user
        Opinion.remove({ user_id }).exec((error) => {
          if (error) { console.log(error); reject({ deleted: false }); }
          else {
            // finally we can remove the user
            User.remove({ _id: user_id }).exec((error) => {
              if (error) { console.log(error); reject({ deleted: false }); }
              else { resolve({ deleted: true }); }
            });
          }
        });
      }
    });
  });
};

/**
 * delete a single discussion
 * @param  {String} discussion_id
 * @return {Promise}
 */
const deleteDiscussion = ({ discussion_id }) => {
  return new Promise((resolve, reject) => {
    // first we need to remove any opinion regarding the discussion
    Opinion.remove({ discussion_id }).exec((error) => {
      if (error) { console.log(error); reject({ deleted: false }); }
      else {
        // now we need to remove the discussion
        Discussion.remove({ _id: discussion_id }).exec((error) => {
          if (error) { console.log(error); reject({ deleted: false }); }
          else { resolve({ deleted: true }); }
        });
      }
    });
  });
};

module.exports = {
  getAdminDashInfo,
  createForum,
  deleteForum,
  deleteUser,
  deleteDiscussion,
};


================================================
FILE: backend/entities/discussion/api.js
================================================
// discussion controllers
const getDiscussion = require('./controller').getDiscussion;
const createDiscussion = require('./controller').createDiscussion;
const toggleFavorite = require('./controller').toggleFavorite;
const deleteDiscussion = require('./controller').deleteDiscussion;

/**
 * discussion apis
 */
const discussionAPI = (app) => {
  // get signle discussion
  app.get('/api/discussion/:discussion_slug', (req, res) => {
    const { discussion_slug } = req.params;
    getDiscussion(discussion_slug).then(
      (result) => { res.send(result); },
      (error) => { res.send(error); }
    );
  });

  // toggle favorite to the discussion
  app.put('/api/discussion/toggleFavorite/:discussion_id', (req, res) => {
    const { discussion_id } = req.params;
    if (req.user) {
      // TODO: describe the toggle process with comments
      toggleFavorite(discussion_id, req.user._id).then(
        (result) => {
          getDiscussion(result.discussion_slug).then(
            (result) => { res.send(result); },
            (error) => { res.send({ discussionUpdated: false }); }
          );
        },
        (error) => { res.send({ discussionUpdated: false }); }
      );
    } else {
      res.send({ discussionUpdated: false });
    }
  });

  // create a new discussion
  app.post('/api/discussion/newDiscussion', (req, res) => {
    if (req.user) {
      createDiscussion(req.body).then(
        (result) => { res.send(Object.assign({}, result._doc, { postCreated: true })); },
        (error) => { res.send({ postCreated: false }); }
      );
    } else {
      res.send({ postCreated: false });
    }
  });

  // delete a discussion
  app.delete('/api/discussion/deleteDiscussion/:discussion_slug', (req, res) => {
    if (req.user) {
      deleteDiscussion(req.params.discussion_slug).then(
        (result) => { res.send({ deleted: true }); },
        (error) => { res.send({ deleted: false }); }
      );
    } else {
      res.send({ deleted: false });
    }
  });
};

module.exports = discussionAPI;


================================================
FILE: backend/entities/discussion/controller.js
================================================
const generateDiscussionSlug = require('../../utilities/tools').generateDiscussionSlug;
const getAllOpinions = require('../opinion/controller').getAllOpinions;
const getUser = require('../user/controller').getUser;

const Discussion = require('./model');
const Opinion = require('../opinion/model');

/**
 * get a single discussion
 * @param  {String} discussion_slug
 * @param  {String} discussion_id
 * @return {Promise}
 */
const getDiscussion = (discussion_slug, discussion_id) => {
  return new Promise((resolve, reject) => {
    let findObject = {};
    if (discussion_slug) findObject.discussion_slug = discussion_slug;
    if (discussion_id) findObject._id = discussion_id;

    Discussion
    .findOne(findObject)
    .populate('forum')
    .populate('user')
    .lean()
    .exec((error, result) => {
      if (error) { console.log(error); reject(error); }
      else if (!result) reject(null);
      else {
        // add opinions to the discussion object
        getAllOpinions(result._id).then(
          (opinions) => {
            result.opinions = opinions;
            resolve(result);
          },
          (error) => { { console.log(error); reject(error); } }
        );
      }
    });
  });
};

/**
 * Create a new discussion
 * @param  {Object} discussion
 * @return {Promise}
 */
const createDiscussion = (discussion) => {
  return new Promise((resolve, reject) => {
    const newDiscussion = new Discussion({
      forum_id: discussion.forumId,
      forum: discussion.forumId,
      user_id: discussion.userId,
      user: discussion.userId,
      discussion_slug: generateDiscussionSlug(discussion.title),
      date: new Date(),
      title: discussion.title,
      content: discussion.content,
      favorites: [],
      tags: discussion.tags,
      pinned: discussion.pinned,
    });

    newDiscussion.save((error) => {
      if (error) {
        console.log(error);
        reject(error);
      }

      resolve(newDiscussion);
    });
  });
};

/**
 * toggle favorite status of discussion
 * @param  {ObjectId} discussion_id
 * @param  {ObjectId} user_id
 * @return {Promise}
 */
const toggleFavorite = (discussion_id, user_id) => {
  return new Promise((resolve, reject) => {
    Discussion.findById(discussion_id, (error, discussion) => {
      if (error) { console.log(error); reject(error); }
      else if (!discussion) reject(null);
      else {
        // add or remove favorite
        let matched = null;
        for (let i = 0; i < discussion.favorites.length; i++) {
          if (String(discussion.favorites[i]) === String(user_id)) {
            matched = i;
          }
        }

        if (matched === null) {
          discussion.favorites.push(user_id);
        } else {
          discussion.favorites = [
            ...discussion.favorites.slice(0, matched),
            ...discussion.favorites.slice(matched + 1, discussion.favorites.length),
          ];
        }

        discussion.save((error, updatedDiscussion) => {
          if (error) { console.log(error); reject(error); }
          resolve(updatedDiscussion);
        });
      }
    });
  });

};

const updateDiscussion = (forum_id, discussion_slug) => {
  // TODO: implement update feature
};

const deleteDiscussion = (discussion_slug) => {
  return new Promise((resolve, reject) => {
    // find the discussion id first
    Discussion
    .findOne({ discussion_slug })
    .exec((error, discussion) => {
      if (error) { console.log(error); reject(error); }

      // get the discussion id
      const discussion_id = discussion._id;

      // remove any opinion regarding the discussion
      Opinion
      .remove({ discussion_id })
      .exec((error) => {
        if (error) { console.log(error); reject(error); }

        // finally remove the discussion
        else {
          Discussion
          .remove({ discussion_slug })
          .exec((error) => {
            if (error) { console.log(error); reject(error); }
            else {
              resolve({ deleted: true });
            }
          });
        }
      });
    });
  });
};

module.exports = {
  getDiscussion,
  createDiscussion,
  updateDiscussion,
  deleteDiscussion,
  toggleFavorite,
};


================================================
FILE: backend/entities/discussion/model.js
================================================
/**
 * discussion model
 */
const mongoose = require('mongoose');

const discussionSchema = mongoose.Schema({
  forum_id: mongoose.Schema.ObjectId,
  forum: { type: mongoose.Schema.ObjectId, ref: 'forum' },
  discussion_slug: String,
  user_id: mongoose.Schema.ObjectId,
  user: { type: mongoose.Schema.ObjectId, ref: 'user' },
  date: Date,
  title: String,
  content: Object,
  favorites: Array,
  tags: Array,
  pinned: Boolean,
});

module.exports = mongoose.model('discussion', discussionSchema);


================================================
FILE: backend/entities/forum/api.js
================================================
// forum controllers
const getAllForums = require('./controller').getAllForums;
const getDiscussions = require('./controller').getDiscussions;

/**
 * forum apis
 */
const forumAPI = (app) => {
  // get all forums
  app.get('/api/forum', (req, res) => {
    getAllForums().then(
      (result) => { res.send(result); },
      (error) => { res.send(error); }
    );
  });

  // get discussions of a forum
  app.get('/api/forum/:forum_id/discussions', (req, res) => {
    getDiscussions(req.params.forum_id, false, req.query.sorting_method).then(
      (result) => { res.send(result); },
      (error) => { res.send([]); }
    );
  });

  // get pinned discussions of a forum
  app.get('/api/forum/:forum_id/pinned_discussions', (req, res) => {
    getDiscussions(req.params.forum_id, true).then(
      (result) => { res.send(result); },
      (error) => { res.send([]); }
    );
  });
};

module.exports = forumAPI;


================================================
FILE: backend/entities/forum/controller.js
================================================
const asyncEach = require('async/each');

// models
const Forum = require('./model');
const Discussion = require('../discussion/model');

// controllers
const getAllOpinions = require('../opinion/controller').getAllOpinions;
const getUser = require('../user/controller').getUser;

/**
 * get all forums list
 * @type {Promise}
 */
const getAllForums = () => {
  return new Promise((resolve, reject) => {
    Forum
    .find({})
    .exec((error, results) => {
      if (error) { console.log(error); reject(error); }
      else if (!results) reject(null);
      else resolve(results);
    });
  });
};

/**
 * get discussions of a forum
 * @param  {ObjectId} forum_id
 * @param  {Boolean} pinned
 * @return {Promise}
 */
const getDiscussions = (forum_id, pinned, sorting_method='date') => {
  return new Promise((resolve, reject) => {
    // define sorthing method
    const sortWith = { };
    if (sorting_method === 'date') sortWith.date = -1;
    if (sorting_method === 'popularity') sortWith.favorites = -1;

    // match discussion id and pinned status
    Discussion
    .find({ forum_id: forum_id, pinned: pinned })
    .sort(sortWith)
    .populate('forum')
    .populate('user')
    .lean()
    .exec((error, discussions) => {
      if (error) { console.error(error); reject(error); }
      else if (!discussions) reject(null);
      else {
        // attach opinion count to each discussion
        asyncEach(discussions, (eachDiscussion, callback) => {
          // add opinion count
          getAllOpinions(eachDiscussion._id).then(
            (opinions) => {
              // add opinion count to discussion doc
              eachDiscussion.opinion_count = opinions ? opinions.length : 0;
              callback();
            },
            (error) => { console.error(error); callback(error); }
          );
        }, (error) => {
          if (error) { console.error(error); reject(error); }
          else resolve(discussions);
        });
      }
    });
  });
};

module.exports = {
  getAllForums,
  getDiscussions,
};


================================================
FILE: backend/entities/forum/model.js
================================================
/**
 * forum model
 */
const mongoose = require('mongoose');

const forumSchema = mongoose.Schema({
  forum_slug: String,
  forum_name: String,
});

module.exports = mongoose.model('forum', forumSchema);


================================================
FILE: backend/entities/opinion/api.js
================================================
// controllers
const getAllOpinions = require('./controller').getAllOpinions;
const createOpinion = require('./controller').createOpinion;
const deleteOpinion = require('./controller').deleteOpinion;

/**
 * opinion apis
 */
const opinionAPI = (app) => {
  // create an opinion
  app.post('/api/opinion/newOpinion', (req, res) => {
    if(req.user) {
      createOpinion(req.body).then(
        (result) => { res.send(result); },
        (error) => { res.send(error); }
      );
    } else {
      res.send({ authenticated: false });
    }
  });

  // remove an opinion
  app.delete('/api/opinion/deleteOpinion/:opinion_id', (req, res) => {
    if(req.user) {
      deleteOpinion(req.params.opinion_id).then(
        (result) => { res.send({ deleted: true }); },
        (error) => { res.send({ deleted: false }); }
      );
    }
  });
};

module.exports = opinionAPI;


================================================
FILE: backend/entities/opinion/controller.js
================================================
// models
const Opinion = require('./model');

/**
 * get all opinion regarding a single discussion
 * @param  {ObjectId} discussion_id
 * @return {Promise}
 */
const getAllOpinions = (discussion_id) => {
  return new Promise((resolve, reject) => {
    Opinion
    .find({ discussion_id })
    .populate('user')
    .sort({ date: -1 })
    .exec((error, opinions) => {
      if (error) { console.log(error); reject(error); }
      else if (!opinions) reject(null);
      else resolve(opinions);
    });
  });
};

/**
 * create an opinion regarding a discussion
 * @param  {ObjectId} forum_id
 * @param  {ObjectId} discussion_id
 * @param  {ObjectId} user_id
 * @param  {Object} content
 * @return {Promise}
 */
const createOpinion = ({ forum_id, discussion_id, user_id, content }) => {
  return new Promise((resolve, reject) => {
    const newOpinion = new Opinion({
      forum_id,
      discussion_id,
      discussion: discussion_id,
      user_id,
      user: user_id,
      content,
      date: new Date(),
    });

    newOpinion.save((error) => {
      if (error) { console.log(error); reject(error); }
      else { resolve(newOpinion); }
    });
  });
};

const updateOpinion = (opinion_id) => {
  // TODO: implement update for opinion
};

/**
 * delete a single opinion
 * @param  {ObjectId} opinion_id
 * @return {Promise}
 */
const deleteOpinion = (opinion_id) => {
  return new Promise((resolve, reject) => {
    Opinion
    .remove({ _id: opinion_id })
    .exec((error) => {
      if (error) { console.log(error); reject(error); }
      else resolve('deleted');
    });
  });
};

module.exports = {
  getAllOpinions,
  createOpinion,
  updateOpinion,
  deleteOpinion,
};


================================================
FILE: backend/entities/opinion/model.js
================================================
/**
 * opinion model
 */
const mongoose = require('mongoose');

const opinionSchema = mongoose.Schema({
  forum_id: mongoose.Schema.ObjectId,
  forum: { type: mongoose.Schema.ObjectId, ref: 'forum' },
  discussion_id: mongoose.Schema.ObjectId,
  discussion: { type: mongoose.Schema.ObjectId, ref: 'discussion' },
  user_id: mongoose.Schema.ObjectId,
  user: { type: mongoose.Schema.ObjectId, ref: 'user' },
  date: Date,
  content: Object,
});

module.exports = mongoose.model('opinion', opinionSchema);


================================================
FILE: backend/entities/user/api.js
================================================
const passport = require('passport');
const signIn = require('./controller').signIn;
const getFullProfile = require('./controller').getFullProfile;

/**
 * user apis
 */
const userAPI = (app) => {
  // get authenticated user
  app.get('/api/user/getUser', (req, res) => {
    if (req.user) res.send(req.user);
    else res.send(null);
  });

  // github authentication route
  app.get(
    '/api/user/authViaGitHub',
    passport.authenticate('github')
  );

  // callback route from github
  app.get(
    // this should match callback url of github app
    '/api/user/authViaGitHub/callback',
    passport.authenticate('github', { failureRedirect: '/signIn/failed' }),
    (req, res) => { res.redirect('/'); }
  );

  // signout the user
  app.get('/api/user/signout', (req, res) => {
    req.logout();
    res.redirect('/');
  });

  // get user full profile
  app.get('/api/user/profile/:username', (req, res) => {
    getFullProfile(req.params.username).then(
      result => { res.send(result); },
      error => { res.send({ error }); }
    );
  });
};

module.exports = userAPI;


================================================
FILE: backend/entities/user/controller.js
================================================
const _ = require('lodash');
const asyncEach = require('async/each');

// controllers
const getAllOpinions = require('../opinion/controller').getAllOpinions;

// models
const User = require('./model');
const Discussion = require('../discussion/model');
const Opinion = require('../opinion/model');

/**
 * get user doc by user id
 * @param  {ObjectId} user_id
 * @return {promise}
 */
const getUser = (user_id) => {
  return new Promise((resolve, reject) => {
    User.findOne({ _id: user_id }, (error, user) => {
      if (error) { console.log(error); reject(error); }
      else if (!user) reject(null);
      else resolve(user);
    });
  });
};

/**
 * sign in/up user via github provided info
 * this will signin the user if user existed
 * or will create a new user using git infos
 * @param  {Object} gitProfile    profile information provided by github
 * @return {promise}              user doc
 */
const signInViaGithub = (gitProfile) => {
  return new Promise((resolve, reject) => {

    // find if user exist on db
    User.findOne({ username: gitProfile.username }, (error, user) => {
      if (error) { console.log(error); reject(error); }
      else {
        // get the email from emails array of gitProfile
        const email = _.find(gitProfile.emails, { verified: true }).value;

        // user existed on db
        if (user) {
          // update the user with latest git profile info
          user.name = gitProfile.displayName;
          user.username = gitProfile.username;
          user.avatarUrl = gitProfile._json.avatar_url;
          user.email = email;
          user.github.id = gitProfile._json.id,
          user.github.url = gitProfile._json.html_url,
          user.github.company = gitProfile._json.company,
          user.github.location = gitProfile._json.location,
          user.github.hireable = gitProfile._json.hireable,
          user.github.bio = gitProfile._json.bio,
          user.github.followers = gitProfile._json.followers,
          user.github.following = gitProfile._json.following,

          // save the info and resolve the user doc
          user.save((error) => {
            if (error) { console.log(error); reject(error); }
            else { resolve(user); }
          });
        }

        // user doesn't exists on db
        else {
          // check if it is the first user (adam/eve) :-p
          // assign him/her as the admin
          User.count({}, (err, count) => {
            console.log('usercount: ' + count);

            let assignAdmin = false;
            if (count === 0) assignAdmin = true;

            // create a new user
            const newUser = new User({
              name: gitProfile.displayName,
              username: gitProfile.username,
              avatarUrl: gitProfile._json.avatar_url,
              email: email,
              role: assignAdmin ? 'admin' : 'user',
              github: {
                id: gitProfile._json.id,
                url: gitProfile._json.html_url,
                company: gitProfile._json.company,
                location: gitProfile._json.location,
                hireable: gitProfile._json.hireable,
                bio: gitProfile._json.bio,
                followers: gitProfile._json.followers,
                following: gitProfile._json.following,
              },
            });

            // save the user and resolve the user doc
            newUser.save((error) => {
              if (error) { console.log(error); reject(error); }
              else { resolve(newUser); }
            });

          });
        }
      }
    });

  });
};

/**
 * get the full profile of a user
 * @param  {String} username
 * @return {Promise}
 */
const getFullProfile = (username) => {
  return new Promise((resolve, reject) => {
    User
    .findOne({ username })
    .lean()
    .exec((error, result) => {
      if (error) { console.log(error); reject(error); }
      else if (!result) reject('not_found');
      else {
        // we got the user, now we need all discussions by the user
        Discussion
        .find({ user_id: result._id })
        .populate('forum')
        .lean()
        .exec((error, discussions) => {
          if (error) { console.log(error); reject(error); }
          else {
            // we got the discussions by the user
            // we need to add opinion count to each discussion
            asyncEach(discussions, (eachDiscussion, callback) => {
              getAllOpinions(eachDiscussion._id).then(
                (opinions) => {
                  // add opinion count to discussion doc
                  eachDiscussion.opinion_count = opinions ? opinions.length : 0;
                  callback();
                },
                (error) => { console.error(error); callback(error); }
              );
            }, (error) => {
              if (error) { console.log(error); reject(error); }
              else {
                result.discussions = discussions;
                resolve(result);
              }
            });
          }
        });
      }
    });
  });
};

module.exports = {
  signInViaGithub,
  getUser,
  getFullProfile,
};


================================================
FILE: backend/entities/user/model.js
================================================
/**
 * user model
 */
const mongoose = require('mongoose');

const userSchema = mongoose.Schema({
  name: String,
  username: String,
  avatarUrl: String,
  email: String,
  role: { type: String, default: 'user' }, // ['admin', 'moderator', 'user']
  github: {
    id: Number,
    url: String,
    company: String,
    location: String,
    bio: String,
    hireable: Boolean,
    followers: Number,
    following: Number,
  },
});

module.exports = mongoose.model('user', userSchema);


================================================
FILE: backend/express.js
================================================
/**
 * module dependencies for express configuration
 */
const passport = require('passport');
const morgan = require('morgan');
const compress = require('compression');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const session = require('express-session');
const mongoStore = require('connect-mongo')(session);
const flash = require('connect-flash');

/**
 * express configuration
 */
const expressConfig = (app, serverConfigs) => {

  // apply gzip compression (should be placed before express.static)
  app.use(compress());

  // log server requests to console
  !serverConfigs.PRODUCTION && app.use(morgan('dev'));

  // get data from html froms
  app.use(bodyParser.json());
  app.use(bodyParser.urlencoded({ extended: true }));

  // read cookies (should be above session)
  app.use(cookieParser());

  // use session with mongo
  app.use(session({
    resave: false,
    saveUninitialized: true,
    secret: 'secret',
    store: new mongoStore({
      url: serverConfigs.DBURL,
      collection : 'sessions',
    }),
  }));

  // use passport session
  app.use(passport.initialize());
  app.use(passport.session());

  // apply passport configs
  require('./passport')(app);

  // connect flash for flash messages (should be declared after sessions)
  app.use(flash());

  // apply development environment additionals
  if (!serverConfigs.PRODUCTION) {
    require('./dev')(app);
  }

  // apply route configs
  require('./routes')(app);
};

module.exports = expressConfig;


================================================
FILE: backend/mockData/discussions.js
================================================

const discussions = [
  {
    'forum_id': '58c23d2efce8810b6f20b0b3',
    'discussion_slug': 'a_pinned_discussion_' + ObjectId,
    'user_id': '58c242a96ba2030d170f86f9',
    'date': 1486450269704,
    'title': 'A pinned discussion',
    'content': 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
    'favorites': 2,
    'tags': ['react', 'redux', 'mongodb'],
    'pinned': true,
  },
  {
    'forum_id': '58c23d2efce8810b6f20b0b3',
    'discussion_slug': 'another_pinned_discussion_' + ObjectId,
    'user_id': '58c242a96ba2030d170f86f9',
    'date': 1486450269704,
    'title': 'Another pinned discussion',
    'content': 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
    'favorites': 3,
    'tags': ['react', 'redux'],
    'pinned': true,
  },
  {
    'forum_id': '58c23d2efce8810b6f20b0b3',
    'discussion_slug': 'one_another_pinned_discussion_' + ObjectId,
    'user_id': '58c242e2fb2e150d2570e02b',
    'date': 1486450269704,
    'title': 'One another pinned discussion',
    'content': 'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
    'favorites': 5,
    'tags': ['express', 'mongodb'],
    'pinned': true,
  },
  {
    'forum_id': '58c23d2efce8810b6f20b0b3',
    'discussion_slug': 'a_discussion_from_general_forum_' + ObjectId,
    'user_id': '58c242e2fb2e150d2570e02b',
    'date': 1486450269704,
    'title': 'A discussion from general forum',
    'content': 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
    'favorites': 2,
    'tags': ['react', 'redux', 'mongodb'],
    'pinned': false,
  },
];

module.exports = discussions;


================================================
FILE: backend/mockData/forum.js
================================================
const forums = [
  {
    'forum_id': 0,
    'forum_slug': 'general',
    'forum_name': 'General',
  },
  {
    'forum_id': 1,
    'forum_slug': 'react',
    'forum_name': 'React',
  },
  {
    'forum_id': 2,
    'forum_slug': 'redux',
    'forum_name': 'Redux',
  },
];

module.exports = forums;


================================================
FILE: backend/mockData/opinions.js
================================================
const opinions = [
  {
    'discussion_id': '58c641904e457708a7147417',
    'user_id': '58c242e2fb2e150d2570e02b',
    'date': 1486450269704,
    'content': 'Awesome stuffs!',
  },
  {
    'discussion_id': '58c641904e457708a7147417',
    'user_id': '58c242e2fb2e150d2570e02b',
    'date': 1486450269704,
    'content': 'Awesome stuffs really!',
  },
  {
    'discussion_id': '58c641cf88336b08c76f3b50',
    'user_id': '58c242a96ba2030d170f86f9',
    'date': 1486450269704,
    'content': 'Great job dude!',
  },
  {
    'discussion_id': '58c641cf88336b08c76f3b50',
    'user_id': '58c242a96ba2030d170f86f9',
    'date': 1486450269704,
    'content': 'These discussions!!!',
  },
];

module.exports = opinions;


================================================
FILE: backend/mockData/users.js
================================================
const users = [
  {
    'user_id': 0,
    'username': 'testuser1',
    'email': 'testuser1@reforum.abc',
    'avatarUrl': 'https://robohash.org/quisapientelibero.png?size=50x50&set=set1',
    'name': 'Test User 1',
  },
  {
    'user_id': 1,
    'username': 'testuser2',
    'email': 'testuser2@reforum.abc',
    'avatarUrl': 'https://robohash.org/magnidictadeserunt.png?size=50x50&set=set1',
    'name': 'Test User 2',
  },
  {
    'user_id': 2,
    'username': 'testuser3',
    'email': 'testuser3@reforum.abc',
    'avatarUrl': 'https://robohash.org/ducimusnostrumillo.jpg?size=50x50&set=set1',
    'name': 'Test User 3',
  },
  {
    'user_id': 3,
    'username': 'testuser4',
    'email': 'testuser4@reforum.abc',
    'avatarUrl': 'https://robohash.org/autemharumvitae.bmp?size=50x50&set=set1',
    'name': 'Test User 4',
  },
  {
    'user_id': 4,
    'username': 'testuser5',
    'email': 'testuser5@reforum.abc',
    'avatarUrl': 'https://robohash.org/similiquealiquidmaiores.jpg?size=50x50&set=set1',
    'name': 'Test User 5',
  },
];

module.exports = users;


================================================
FILE: backend/passport.js
================================================
/**
 * module dependencies for passport configuration
 */
const passport = require('passport');
const GitHubStrategy = require('passport-github').Strategy;

const GITHUB_CLIENT_ID = require('../config/credentials').GITHUB_CLIENT_ID;
const GITHUB_CLIENT_SECRET = require('../config/credentials').GITHUB_CLIENT_SECRET;
const GITHUB_CALLBACK_URL = require('../config/credentials').GITHUB_CALLBACK_URL;

// controllers
const getUser = require('./entities/user/controller').getUser;
const signInViaGithub = require('./entities/user/controller').signInViaGithub;

/**
 * passport configuration
 */
const passportConfig = (app) => {
  passport.serializeUser((user, done) => {
    done(null, user._id);
  });

  passport.deserializeUser((id, done) => {
    getUser(id).then(
      (user) => { done(null, user); },
      (error) => { done(error); }
    );
  });

  // github strategy for passport using OAuth
  passport.use(new GitHubStrategy(
    {
      clientID: GITHUB_CLIENT_ID,
      clientSecret: GITHUB_CLIENT_SECRET,
      callbackURL: GITHUB_CALLBACK_URL,
      scope: 'user:email',
    },
    (accessToken, refreshToken, gitProfile, done) => {
      signInViaGithub(gitProfile).then(
        (user) => { console.log('got the user'); done(null, user); },
        (error) => { console.log('something error occurs'); done(error); }
      );
    }
  ));
};

module.exports = passportConfig;


================================================
FILE: backend/routes.js
================================================
/**
 * module dependencies for routes configuration
 */
const path = require('path');
const express = require('express');

const userAPI = require('./entities/user/api');
const forumAPI = require('./entities/forum/api');
const discussionAPI = require('./entities/discussion/api');
const opinionAPI = require('./entities/opinion/api');
const adminAPI = require('./entities/admin/api');

/**
 * routes configurations
 */
const routesConfig = (app) => {
  // serves static files from public directory
  const publicPath = path.resolve(__dirname, '../public');
  app.use(express.static(publicPath));

  // serve api endpoint
  app.get('/api', (req, res) => {
    res.send('Hello from API endpoint');
  });

  // apply user apis
  userAPI(app);

  // apply forum apis
  forumAPI(app);

  // apply discussion apis
  discussionAPI(app);

  // apply opinion apis
  opinionAPI(app);

  // apply admin apis
  adminAPI(app);

  // all get request will send index.html for react-router
  // to handle the route request
  app.get('*', (req, res) => {
    res.sendFile(path.resolve(__dirname, '../public', 'index.html'));
  });
};

module.exports = routesConfig;


================================================
FILE: backend/utilities/tools.js
================================================
/**
 * Search object properties recursively and
 * perform callback action on each
 * @param  {Object}   obj      [object to search props]
 * @param  {Function} callback [action to perform on each props, two parameters (property, object)]
 * @return {Object}            [new modified object]
 */
const deepPropSearch = (obj, callback) => {
  // new object for immutability
  const newObj = Object.assign({}, obj);

  // recursive search function
  const deepSearch = (obj) => {
    for (const prop in obj) {
      // perform callback for each property
      callback && callback(prop, obj);

      // recursive search inside objects/arrays
      if (typeof obj[prop] === 'object') {
        if (obj[prop].length && obj[prop].length > 0) {
          obj[prop].forEach((deepObj) => {
            deepSearch(deepObj);
          });
        } else {
          deepSearch(obj[prop]);
        }
      }
    }
  };

  // start deep searching for properties
  deepSearch(newObj, callback);

  // return new object, maintain immutability
  return newObj;
};

const generateDiscussionSlug = (discussionTitle) => {
  const ObjectId = require('mongoose').Types.ObjectId();
  return discussionTitle.replace(/[^a-z0-9]/gi, '_').toLowerCase() + '_' + ObjectId;
};

module.exports = {
  deepPropSearch,
  generateDiscussionSlug,
};


================================================
FILE: config/credentials.js
================================================
module.exports = {
  GITHUB_CLIENT_ID: '',
  GITHUB_CLIENT_SECRET: '',
  GITHUB_CALLBACK_URL: '',
  DBURL: '',
};


================================================
FILE: config/serverConfig.js
================================================
/**
 * module dependencies for server configuration
 */
const path = require('path');
const databaseUrl = require('./credentials').DBURL;

/**
 * server configurations
 */
const serverConfigs = {
  PRODUCTION: process.env.NODE_ENV === 'production',
  PORT: process.env.PORT || 8080,
  ROOT: path.resolve(__dirname, '..'),
  DBURL: databaseUrl,
};

module.exports = serverConfigs;


================================================
FILE: config/webpack.dev.config.js
================================================
/**
 * module dependencies for webpack dev configuration
 */
const path = require('path');
const webpack = require('webpack');

// define paths
const nodeModulesPath = path.resolve(__dirname, '../node_modules');
const buildPath = path.resolve(__dirname, '../public', 'build');
const mainAppPath = path.resolve(__dirname, '../frontend', 'App', 'index.js');
const sharedStylesPath = path.resolve(__dirname, '../frontend', 'SharedStyles');
const componentsPath = path.resolve(__dirname, '../frontend', 'Components');
const containersPath = path.resolve(__dirname, '../frontend', 'Containers');
const viewsPath = path.resolve(__dirname, '../frontend', 'Views');

/**
 * webpack development configuration
 */
module.exports = {
  target  : 'web',
  devtool: 'inline-source-map',

  entry: [
    'webpack-hot-middleware/client',
    mainAppPath,
  ],

  output: {
    filename: 'bundle.js',
    path: buildPath,
    publicPath: '/build/',
  },

  module: {
    loaders: [
      {
        test: /\.js$/,
        loaders: [ 'react-hot', 'babel-loader' ],
        exclude: [nodeModulesPath],
      },
      {
        test: /\.css$/,
        loaders: [
          'style-loader',
          'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]',
          'postcss-loader?sourceMap=inline',
        ],
      },
      { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' },
      { test: /\.svg$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' },
    ],
  },

  postcss: [ require('autoprefixer'), require('postcss-nesting') ],

  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin(),
  ],

  resolve : {
    extensions: ['', '.js', '.css'],
    alias: {
      SharedStyles: sharedStylesPath,
      Components: componentsPath,
      Containers: containersPath,
      Views: viewsPath,
    },
  },
};


================================================
FILE: config/webpack.prod.config.js
================================================
/**
 * module dependencies for webpack production configuration
 */
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');

// define paths
const nodeModulesPath = path.resolve(__dirname, '../node_modules');
const buildPath = path.resolve(__dirname, '../public', 'build');
const mainAppPath = path.resolve(__dirname, '../frontend', 'App', 'index.js');
const sharedStylesPath = path.resolve(__dirname, '../frontend', 'SharedStyles');
const componentsPath = path.resolve(__dirname, '../frontend', 'Components');
const containersPath = path.resolve(__dirname, '../frontend', 'Containers');
const viewsPath = path.resolve(__dirname, '../frontend', 'Views');

/**
 * webpack production configuration
 */
module.exports = {
  target  : 'web',

  entry: [
    mainAppPath,
  ],

  output: {
    filename: 'bundle.js',
    path: buildPath,
  },

  module: {
    loaders: [
      {
        test: /\.js$/,
        loaders: [ 'react-hot', 'babel-loader' ],
        exclude: [nodeModulesPath],
      },
      {
        test: /\.css$/,
        loader: ExtractTextPlugin.extract(
          'style-loader',
          'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!postcss-loader?sourceMap=inline'
        ),
      },
      { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' },
      { test: /\.svg$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' },
    ],
  },

  postcss: [ require('autoprefixer'), require('postcss-nesting') ],

  plugins: [
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.optimize.UglifyJsPlugin(),
    new ExtractTextPlugin('style.css', { allChunks: true }),
  ],

  resolve: {
    extensions: ['', '.js', '.css'],
    alias: {
      SharedStyles: sharedStylesPath,
      Components: componentsPath,
      Containers: containersPath,
      Views: viewsPath,
    },
  },

  externals: {
    'react': 'React',
    'react-dom': 'ReactDOM',
    'redux': 'Redux',
    'react-router': 'ReactRouter',
    'moment': 'moment',
  },
};


================================================
FILE: docs/api.md
================================================
# API Docs

Work in progress :-)


================================================
FILE: docs/system_overview.md
================================================
# System Overview

Work in progress :-)


================================================
FILE: frontend/App/Admin.js
================================================
import React, { Component } from 'react';
import { Link, browserHistory } from 'react-router';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';

import { getUser } from './actions';

import AdminHeader from 'Containers/AdminHeader';
import appLayout from 'SharedStyles/appLayout.css';
import styles from './styles.css';

class AdminContainer extends Component {
  componentDidMount() {
    // fetch the user
    this.props.getUser();
  }

  render() {
    const { user } = this.props;

    if (user.fetchingUser) {
      return (
        <div style={{ textAlign: 'center', marginTop: 20 }}>
          Loading users profile...
        </div>
      );
    }

    if (user.role === 'admin') {
      return (
        <div>
          <Helmet><title>ReForum | Admin</title></Helmet>
          <AdminHeader />
          {this.props.children}
        </div>
      );
    }
    else {
      return (
        <div style={{ textAlign: 'center', marginTop: 20 }}>
          We are cordially sorry that you are not allowed to view admin panel!<br />
          Please go back to <Link to='/'>root</Link> page.
        </div>
      );
    }

    return (
      <div style={{ textAlign: 'center', marginTop: 20 }}>
        Something went wrong.<br />
        Please go back to <Link to='/'>root</Link> page.
      </div>
    );
  }
}

export default connect(
  (state) => { return {
    user: state.user,
  }; },
  (dispatch) => { return {
    getUser: () => { dispatch(getUser()); },
  }; }
)(AdminContainer);


================================================
FILE: frontend/App/App.js
================================================
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';

import Header from 'Containers/Header';
import Footer from 'Components/Footer';
import appLayout from 'SharedStyles/appLayout.css';
import styles from './styles.css';

import { getForums, updateCurrentForum, getUser } from './actions';

class AppContainer extends Component {
  componentDidMount() {
    const {
      params,
      updateCurrentForum,
      getForums,
      getUser,
    } = this.props;

    // get all forum list
    getForums();

    // check for authenticated user
    getUser();

    // set current forum based on route
    const currentForum = params.forum || '';
    updateCurrentForum(currentForum);
  }

  componentDidUpdate() {
    const {
      forums,
      params,
      currentForum,
      updateCurrentForum,
    } = this.props;

    let newCurrentForum = '';
    if (params.forum) newCurrentForum = params.forum;
    else if (forums) newCurrentForum = forums[0].forum_slug;

    // update current forum if necessery
    if (newCurrentForum !== currentForum) updateCurrentForum(newCurrentForum);
  }

  render() {
    const { forums } = this.props;

    // render only if we get the forum lists
    if (forums) {
      return (
        <div>
          <Helmet><title>ReForum</title></Helmet>

          <div className={styles.gitForkTag}>
            <a className={styles.gitLink} href="https://github.com/shoumma/ReForum" target="_blank">Fork on Github</a>
          </div>

          <Header />
          {this.props.children}
          <Footer />
        </div>
      );
    }

    return (
      <div className={styles.loadingWrapper}>Loading...</div>
    );
  }
}

export default connect(
  (state) => { return {
    forums: state.app.forums,
    currentForum: state.app.currentForum,
  }; },
  (dispatch) => { return {
    getForums: () => { dispatch(getForums()); },
    updateCurrentForum: (currentForum) => { dispatch(updateCurrentForum(currentForum)); },
    getUser: () => { dispatch(getUser()); },
  }; }
)(AppContainer);


================================================
FILE: frontend/App/actions.js
================================================
import _ from 'lodash';
import {
  START_FETCHING_FORUMS,
  STOP_FETCHING_FORUMS,
  FETCHING_FORUMS_SUCCESS,
  FETCHING_FORUMS_FAILURE,
  UPDATECURRENTFORUM,
  START_FETCHING_USER,
  FETCHING_USER_SUCCESS,
  FETCHING_USER_FAILURE,
} from './constants';
import {
  fetchForums,
  fetchUser,
  signOut,
} from './api';

/**
 * get all forum list
 * @return {action}
 */
export const getForums = () => {
  return (dispatch, getState) => {
    dispatch({ type: START_FETCHING_FORUMS });

    fetchForums().then(
      data => dispatch({ type: FETCHING_FORUMS_SUCCESS, payload: data.data }),
      error => dispatch({ type: FETCHING_FORUMS_FAILURE })
    );
  };
};

/**
 * update current forum when route change occurs
 * @param  {String} currentForum
 * @return {action}
 */
export const updateCurrentForum = (currentForum) => {
  return {
    type: UPDATECURRENTFORUM,
    payload: currentForum,
  };
};

/**
 * get the current user from server
 * @return {action}
 */
export const getUser = () => {
  return (dispatch, getState) => {
    dispatch({ type: START_FETCHING_USER });

    fetchUser().then(
      data => {
        if (!data.data._id) dispatch({ type: FETCHING_USER_FAILURE });
        else dispatch({ type: FETCHING_USER_SUCCESS, payload: data.data });
      },
      error => dispatch({ type: FETCHING_USER_FAILURE })
    );
  };
};


================================================
FILE: frontend/App/api.js
================================================
import axios from 'axios';

export const fetchForums = (forum_id) => {
  return axios.get('/api/forum');
};

export const fetchUser = () => {
  return axios.get('/api/user/getUser');
};


================================================
FILE: frontend/App/constants.js
================================================
export const UPDATECURRENTFORUM = 'update_current_forum';

export const START_FETCHING_FORUMS = 'start_fetching_forums';
export const STOP_FETCHING_FORUMS = 'stop_fetching_forums';
export const FETCHING_FORUMS_SUCCESS = 'fetching_forums_success';
export const FETCHING_FORUMS_FAILURE = 'fetching_forums_failure';

export const START_FETCHING_USER = 'start_fetching_user';
export const FETCHING_USER_SUCCESS = 'fetching_user_success';
export const FETCHING_USER_FAILURE = 'fetching_user_failure';
export const SIGNOUT_USER_SUCCESS = 'signOut_user_success';
export const SIGNOUT_USER_FAILURE = 'signOut_user_failure';


================================================
FILE: frontend/App/index.js
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, browserHistory, IndexRoute } from 'react-router';
import { Provider } from 'react-redux';
import styles from './styles';

// app store
import appStore from './store';

// app views
import AppContainer from './App';
import AdminContainer from './Admin';
import Dashboard from '../Views/AdminDashboard';
import Header from 'Containers/Header';
import ForumFeed from '../Views/ForumFeed';
import SingleDiscussion from '../Views/SingleDiscussion';
import NewDiscussion from '../Views/NewDiscussion';
import UserProfile from '../Views/UserProfile';
import NotFound from '../Views/NotFound';

ReactDOM.render (
  <Provider store={appStore}>
    <Router history={browserHistory}>
      <Route path="/admin" component={AdminContainer}>
        <IndexRoute component={Dashboard} />
      </Route>
      <Route path="/" component={AppContainer}>
        <IndexRoute component={ForumFeed} />
        <Route path=":forum" component={ForumFeed} />
        <Route path=":forum/discussion/:discussion" component={SingleDiscussion} />
        <Route path=":forum/new_discussion" component={NewDiscussion} />
        <Route path="user/:username" component={UserProfile} />
        <Route path="*" component={NotFound} />
      </Route>
    </Router>
  </Provider>,
  document.getElementById('root')
);


================================================
FILE: frontend/App/reducers.js
================================================
import {
  START_FETCHING_FORUMS,
  STOP_FETCHING_FORUMS,
  FETCHING_FORUMS_SUCCESS,
  FETCHING_FORUMS_FAILURE,
  UPDATECURRENTFORUM,
  START_FETCHING_USER,
  FETCHING_USER_SUCCESS,
  FETCHING_USER_FAILURE,
} from './constants';

const initialState = {
  fetchingForums: false,
  forums: null,
  currentForum: 'general',
  error: false,
};

/**
 * reducer for top level app state
 */
export const appReducer = (state = initialState, action) => {
  switch (action.type) {
    case START_FETCHING_FORUMS:
      return Object.assign({}, state, {
        fetchingForums: true,
      });;

    case STOP_FETCHING_FORUMS:
      return Object.assign({}, state, {
        fetchingForums: false,
      });;

    case FETCHING_FORUMS_SUCCESS:
      return Object.assign({}, state, {
        forums: action.payload,
        fetchingForums: false,
        error: false,
      });

    case FETCHING_FORUMS_FAILURE:
      return Object.assign({}, state, {
        fetchingForums: false,
        error: 'Unable to fetch forums',
      });

    case UPDATECURRENTFORUM:
      return Object.assign({}, state, {
        currentForum: action.payload,
      });

    default:
      return state;
  }
};

/**
 * reducer for user
 */
const initialUserState = {
  fetchingUser: true,
  authenticated: false,
  error: null,
  _id: null,
  name: null,
  email: null,
  username: null,
  avatarUrl: null,
  githubUrl: null,
  githubLocation: null,
  githubBio: null,
  role: null,
};

export const userReducer = (state = initialUserState, action) => {
  switch (action.type) {
    case START_FETCHING_USER:
      return Object.assign({}, state, {
        fetchUser: true,
      });

    case FETCHING_USER_SUCCESS:
      const {
        _id,
        name,
        username,
        avatarUrl,
        email,
        githubBio,
        githubUrl,
        githubLocation,
        role,
      } = action.payload;

      return Object.assign({}, state), {
        fetchingUser: false,
        authenticated: true,
        error: null,
        _id,
        name,
        username,
        avatarUrl,
        email,
        githubBio,
        githubUrl,
        githubLocation,
        role,
      };

    case FETCHING_USER_FAILURE:
      return Object.assign({}, initialUserState, {
        fetchingUser: false,
        error: 'Unable to fetch user!',
      });

    default:
      return state;
  }
};


================================================
FILE: frontend/App/store.js
================================================
import { createStore, applyMiddleware, compose } from 'redux';
import { combineReducers } from 'redux';
import thunk from 'redux-thunk';

import { appReducer, userReducer } from './reducers';
import { feedReducer } from '../Views/ForumFeed/reducers';
import { singleDiscussionReducer } from '../Views/SingleDiscussion/reducers';
import { newDiscussionReducer } from '../Views/NewDiscussion/reducers';
import { adminInfoReducer } from '../Views/AdminDashboard/reducers';
import { userProfileReducer } from '../Views/UserProfile/reducers';

// root reducer for app
const rootReducer = combineReducers({
  user: userReducer,
  app: appReducer,
  feed: feedReducer,
  discussion: singleDiscussionReducer,
  newDiscussion: newDiscussionReducer,
  adminInfo: adminInfoReducer,
  userProfile: userProfileReducer,
});

// dev tool extension
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

// application store
let store = createStore(
  rootReducer,
  /* preloaded state, */
  composeEnhancers(
    applyMiddleware(thunk)
  )
);

export default store;


================================================
FILE: frontend/App/styles.css
================================================
/**
 * Application container stylings
 */

.loadingWrapper {
  height: 100vh;
  width: 100vw;
  display: flex;
  justify-content: center;
  align-items: center;
}

.gitForkTag {
  position: fixed;
  bottom:0px;
  right: 0px;

  background-color: #f9f9f9;
  border-top: 1px solid #e8e8e8;
  border-bottom: 1px solid #e8e8e8;

  padding: 5px 50px;
  transform: rotate(-45deg) translateY(-5px) translateX(54px);

  z-index: 1;
}

.gitLink {
  text-decoration: none;
  color: black;
}


================================================
FILE: frontend/Components/Button/index.js
================================================
import React, { Component } from 'react';
import classnames from 'classnames';
import styles from './styles';

class Button extends Component {
  render() {
    const {
      type,
      fullWidth,
      noUppercase,
      className,
      style,
      onClick,
      alwaysActive,
    } = this.props;

    return (
      <button
        onClick={onClick}
        className={classnames(
          styles.button,
          styles.buttonDefaults,
          styles[type],
          fullWidth && styles.fullWidth,
          noUppercase && styles.noUppercase,
          alwaysActive && styles.alwaysActive,
          className
        )}
        style={style}
      >
        {this.props.children}
      </button>
    );
  }
}

Button.defaultProps = {
  type: 'default',
  fullWidth: false,
  noUppercase: false,
  alwaysActive: false,
  className: '',
  style: {},
  onClick: () => { },
};

Button.propTypes = {
  type: React.PropTypes.oneOf(['default', 'outline']),
  fullWidth: React.PropTypes.bool,
  noUppercase: React.PropTypes.bool,
  alwaysActive: React.PropTypes.bool,
  className: React.PropTypes.string,
  style: React.PropTypes.object,
  onClick: React.PropTypes.func,
};

export default Button;


================================================
FILE: frontend/Components/Button/styles.css
================================================
@value borderColor, secondaryFontColor, primaryFontColor from 'SharedStyles/globalStyles.css';

/**
 * Button styles
 */

/* resetting all default button styles */
button {
  background: none;
  border: 0;
  color: inherit;
  font: inherit;
  line-height: normal;
  overflow: visible;
  padding: 0;
  user-select: none;
  outline: none;
  cursor: pointer;
}

.buttonDefaults {
  padding: 10px 20px;
  text-transform: uppercase;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.5px;
}

.default {
  color: secondaryFontColor;
  transition: color 0.3s;
  &:hover { color: primaryFontColor; }
}

.outline {
  color: secondaryFontColor;
  border: 1px solid borderColor;
  transition: color 0.3s, border 0.3s;
  &:hover {
    color: primaryFontColor;
    border: 1px solid secondaryFontColor;
  }
}

.fullWidth { width: 100%; }
.noUppercase { text-transform: none; }
.alwaysActive {
  color: primaryFontColor;
  border-color: primaryFontColor;
}


================================================
FILE: frontend/Components/Dashboard/Counts/index.js
================================================
import React, { Component } from 'react';
import classnames from 'classnames';
import styles from './styles';

class Counts extends Component {
  render() {
    const {
      count,
      label,
    } = this.props;

    return (
      <div className={styles.container}>
        <div className={styles.count}>{count}</div>
        <div className={styles.label}>{label}</div>
      </div>
    );
  }
}

Counts.defaultProps = {
  count: 0,
  label: 'default',
};

Counts.propTypes = {
  count: React.PropTypes.number,
  label: React.PropTypes.string,
};

export default Counts;


================================================
FILE: frontend/Components/Dashboard/Counts/styles.css
================================================
@value primaryFontColor, secondaryFontColor, borderColor from 'SharedStyles/globalStyles.css';
@value smallBP from 'SharedStyles/globalStyles.css';

.container {
  flex: 1;
  margin: 0px 10px 10px 10px;
  padding: 20px 10px;
  max-width: 200px;

  display: flex;
  align-items: center;

  border: 1px solid borderColor;
}

.count {
  font-size: 40px;
  font-weight: lighter;
}

.label {
  margin-left: 20px;
}


================================================
FILE: frontend/Components/Dashboard/ForumBox/index.js
================================================
import React, { Component } from 'react';
import classnames from 'classnames';
import styles from './styles';

import Button from 'Components/Button';

class ForumBox extends Component {
  constructor(props) {
    super(props);
    this.state = {
      newForumTitle: '',
      newForumSlug: '',
      errorMsg: null,
    };

    this.handleCreateForum = this.handleCreateForum.bind(this);
  }

  handleCreateForum() {
    // remove any error messages
    this.setState({ errorMsg: null });

    const {
      newForumTitle,
      newForumSlug,
    } = this.state;

    let convertedTitle = null;
    let convertedSlug = null;

    // check and convert forum title
    if (newForumTitle !== '') {
      // trim any leading or ending white spaces
      convertedTitle = newForumTitle.trim();

      // check the length, 4 is hardcoded here
      if (convertedTitle.length < 4) {
        return this.setState({ errorMsg: 'Forum title should have at least 4 charecters.' });
      }
    } else {
      return this.setState({ errorMsg: 'Forum title is empty. Please provide a valid Forum Title.' });
    }

    // check and confirm forum slug
    if (convertedSlug !== '') {
      const slugRegex = /^[a-z\_]+$/;
      convertedSlug = newForumSlug.match(slugRegex) ? newForumSlug : null;

      // length check
      if (convertedSlug && convertedSlug.length < 4) {
        return this.setState({ errorMsg: 'Forum slug should have at least 4 charecters.' });
      }
    } else {
      return this.setState({ errorMsg: 'Forum slug is empty. Please provide a valid Forum Slug.' });
    }

    if (!convertedTitle) { return this.setState({ errorMsg: 'Please provide a valid Forum Title.' }); }
    if (!convertedSlug) { return this.setState({ errorMsg: 'Please provide a valid Forum Slug. Slug can only contain small case alphabets and underscore.' }); }

    if (convertedTitle && convertedSlug) { this.props.createAction({ title: convertedTitle, slug: convertedSlug }); }
  }

  render() {
    const {
      forums,
      creatingForum,
      deleteAction,
      deletingForum,
    } = this.props;

    const {
      newForumTitle,
      newForumSlug,
      errorMsg,
    } = this.state;

    return (
      <div className={styles.container}>
        <div className={styles.title}>Current Forums</div>
        <div className={styles.forumsContainer}>
          { deletingForum && <div className={styles.loadingMsg}>Removing forum, please wait...</div> }

          { forums.map((forum) => <div key={forum.id} className={styles.forum}>
            <div className={styles.forumTitle}>{ forum.name }</div>
            <div className={styles.forumSlug}>({ forum.slug })</div>
            <div className={styles.removeButton}>
              <Button onClick={() => { deleteAction(forum.id); }}>Remove</Button>
            </div>
          </div>) }

        </div>

        <div className={styles.createForumContainer}>
          { creatingForum && <div className={styles.loadingMsg}>Creating forum, please wait...</div> }
          <div className={styles.createTitle}>Create New Forum</div>
          <div className={styles.createForum}>
            <div className={styles.createInputContainer}>
              <div className={styles.inputLabel}>Title: </div>
              <input
                type={'text'}
                className={styles.createInput}
                placeholder={'Forum Title'}
                onChange={(e) => { this.setState({ newForumTitle: e.target.value }); }}
              />
            </div>
            <div className={styles.createInputContainer}>
              <div className={styles.inputLabel}>Slug: </div>
              <input
                type={'text'}
                className={styles.createInput}
                placeholder={'forum_slug'}
                onChange={(e) => { this.setState({ newForumSlug: e.target.value }); }}
              />
            </div>
            <Button onClick={this.handleCreateForum}>Create</Button>
          </div>
          { errorMsg && <div className={styles.errorMsg}>{errorMsg}</div> }
        </div>
      </div>
    );
  }
}

ForumBox.defaultProps = {
};

ForumBox.propTypes = {
  forums: React.PropTypes.array,
  deletingForum: React.PropTypes.bool,
  deleteAction: React.PropTypes.func,
  creatingForum: React.PropTypes.bool,
  createAction: React.PropTypes.func,
};

export default ForumBox;


================================================
FILE: frontend/Components/Dashboard/ForumBox/styles.css
================================================
@value primaryFontColor, secondaryFontColor, borderColor from 'SharedStyles/globalStyles.css';
@value smallBP from 'SharedStyles/globalStyles.css';

.container {
  display: flex;
  flex-direction: column;
  border: 1px solid borderColor;
}

.title {
  padding: 10px 10px;
  border-bottom: 1px solid borderColor;
}

.forumsContainer {
  position: relative;

  & .forum {
    padding: 0px 20px;
    display: flex;
    align-items: center;
    border-bottom: 1px solid borderColor;

    & .forumTitle { font-weight: bold; }
    & .forumSlug { color: secondaryFontColor; margin-left: 10px; }
  }
}

.createForumContainer {
  position: relative;

  & .createTitle {
    border-bottom: none;
    padding: 10px 10px 0px 10px;
  }

  & .createForum {
    padding: 10px 10px;
    display: flex;
    align-items: center;

    & .createInputContainer {
      display: flex;
      align-items: center;
      margin-left: 20px;
      &:first-child { margin-left: 0px; }

      & .inputLabel { margin-right: 10px; }
      & .createInput { outline: none; padding: 5px 10px; }
    }
  }
}


.errorMsg {
  font-weight: bold;
  padding: 5px 10px 10px 10px;
  color: rgba(231, 76, 60,1.0);
  font-size: 12px;
  line-height: 14px;
}

.loadingMsg {
  position: absolute;
  top: 0px;
  left: 0px;
  width: 100%;
  height: 100%;
  background-color: rgba(255, 255, 255, 0.9);
  border-bottom: 1px solid borderColor;

  display: flex;
  justify-content: center;
  align-items: center;
}


================================================
FILE: frontend/Components/FeedBox/DiscussionBox/index.js
================================================
import React, { Component } from 'react';
import { Link } from 'react-router';
import classnames from 'classnames';
import Moment from 'moment';
import styles from './styles';

import Tag from 'Components/Tag';

class DiscussionBox extends Component {
  render() {
    const {
      voteCount,
      userName,
      userGitHandler,
      discussionTitle,
      time,
      opinionCount,
      tags,
      link,
      userProfile,
    } = this.props;

    const postTime = Moment(time);
    const timeDisplay = postTime.from(Moment());

    return (
      <div className={styles.container}>
        <div className={classnames(styles.title, userProfile && styles.titleBottomMargin)}><Link to={link}>{discussionTitle}</Link></div>

        { !userProfile && <div className={styles.posterInfo}>
          <Link to={`/user/${userGitHandler}`} className={styles.name}>{userName}</Link>
          <a target="_blank" href={`https://www.github.com/${userGitHandler}`} className={styles.gitHandler}>
            - <i className={classnames('fa fa-github-alt', styles.gitIcon)}></i> {userGitHandler}
          </a>
        </div> }

        <div className={styles.boxFooter}>
          <div className={styles.tagsArea}>
            { tags.map((tag) => <Tag key={tag} name={tag} />) }
          </div>

          <div className={styles.postInfo}>
            <span className={styles.info}>{timeDisplay}</span>
            <span className={styles.info}>{voteCount} favorites</span>
            <span className={styles.info}>{opinionCount} opinions</span>
          </div>
        </div>
      </div>
    );
  }
}

DiscussionBox.defaultProps = {
  discussionId: 1,
  voteCount: 20,
  userName: 'Hello World',
  userGitHandler: 'github',
  discussionTitle: 'This is a default post title',
  time: Moment(),
  opinionCount: 12,
  tags: ['react', 'redux', 'nodejs'],
  link: '',
  userProfile: false,
};

DiscussionBox.propTypes = {
  discussionId: React.PropTypes.number,
  voteCount: React.PropTypes.number,
  userName: React.PropTypes.string,
  userGitHandler: React.PropTypes.string,
  discussionTitle: React.PropTypes.string,
  time: React.PropTypes.any,
  opinionCount: React.PropTypes.number,
  tags: React.PropTypes.array,
  link: React.PropTypes.string,
  userProfile: React.PropTypes.bool,
};

export default DiscussionBox;


================================================
FILE: frontend/Components/FeedBox/DiscussionBox/styles.css
================================================
@value primaryFontColor, secondaryFontColor, borderColor, backShade from 'SharedStyles/globalStyles';
@value smallBP from 'SharedStyles/globalStyles';

/**
 * PostBox styles
 */

.container {
  display: flex;
  flex-direction: column;
  padding: 10px;
  border-bottom: 1px solid borderColor;
  &:last-child { border-bottom: none; }
}

.title {
  font-size: 18px;
  font-weight: 300;

  & a {
    text-decoration: none;
    color: primaryFontColor;
  }
}

.titleBottomMargin { margin-bottom: 10px; }

.posterInfo {
  margin-top: 10px;
  font-size: 13px;
  & .name {
    font-weight: 700;
    color: secondaryFontColor;
    text-decoration: none;
    transition: color 0.3s;

    &:hover { color: primaryFontColor; }
  }
  & .gitHandler {
    margin-left: 5px;
    text-decoration: none;
    color: secondaryFontColor;
    transition: color 0.3s;

    & .gitIcon {
      font-size: 16px;
      padding: 0 5px;
    }

    &:hover { color: primaryFontColor; }
  }
}

.boxFooter {
  margin-top: 10px;
  display: flex;
  align-items: center;
  justify-content: space-between;

  @media smallBP {
    margin-top: 0px;
    flex-direction: column-reverse;
    align-items: flex-start;
  }
}

.tagsArea {
  display: flex;
  align-items: center;

  @media smallBP { margin-top: 5px; }
}

.postInfo {
  & .info {
    margin-left: 20px;
    font-size: 12px;
    font-weight: 700;
    text-transform: lowercase;
    color: secondaryFontColor;

    &:first-child { margin-left: 0px; }
  }
}


================================================
FILE: frontend/Components/FeedBox/index.js
================================================
import React, { Component } from 'react';
import classnames from 'classnames';
import Moment from 'moment';
import styles from './styles';

import DiscussionBox from './DiscussionBox';

class FeedBox extends Component {
  renderSort() {
    const {
      activeSortingMethod,
      onChangeSortingMethod,
    } = this.props;

    if (this.props.type === 'general') {
      return (
        <div className={styles.sortList}>
          <span
            className={classnames(styles.sort, (activeSortingMethod === 'date') && styles.sortActive)}
            onClick={() => onChangeSortingMethod('date')}
          >
            Latest
          </span>
          <span
            className={classnames(styles.sort, (activeSortingMethod === 'popularity') && styles.sortActive)}
            onClick={() => onChangeSortingMethod('popularity')}
          >
            Popular
          </span>
        </div>
      );
    }
    return null;
  }

  renderEmptyDiscussionLine(loading, discussions) {
    if (!loading) {
      if (!discussions || discussions.length === 0) {
        return <div className={styles.loading}>No discussions...</div>;
      }
    }
  }

  render() {
    const {
      type,
      loading,
      discussions,
      currentForum,
      userProfile,
    } = this.props;

    let discussionBoxTitle = '';
    if (type === 'general') discussionBoxTitle = 'Discussions';
    if (type === 'pinned') discussionBoxTitle = 'Pinned';

    return (
      <div className={styles.container}>
        <div className={styles.header}>
          <span className={styles.title}>{discussionBoxTitle}</span>
          { !userProfile && this.renderSort() }
        </div>
        { loading && <div className={styles.loading}>Loading...</div> }
        { this.renderEmptyDiscussionLine(loading, discussions) }
        { !loading &&
          <div className={styles.discussions}>
            { discussions && discussions.map((discussion) =>
              <DiscussionBox
                userProfile={userProfile}
                key={discussion._id}
                userName={discussion.user.name || discussion.user.username}
                userGitHandler={discussion.user.username}
                discussionTitle={discussion.title}
                time={discussion.date}
                tags={discussion.tags}
                opinionCount={discussion.opinion_count}
                voteCount={discussion.favorites.length}
                link={`/${userProfile ? discussion.forum.forum_slug : currentForum}/discussion/${discussion.discussion_slug}`}
              />
            ) }
          </div>
        }
      </div>
    );
  }
}

FeedBox.defaultProps = {
  type: 'general',
  loading: false,
  discussions: [],
  currentForum: 'general',
  activeSortingMethod: 'date',
  onChangeSortingMethod: (val) => { },
  userProfile: false,
};

FeedBox.propTypes = {
  type: React.PropTypes.oneOf(['general', 'pinned']),
  loading: React.PropTypes.bool,
  discussions: React.PropTypes.array,
  currentForum: React.PropTypes.string,
  activeSortingMethod: React.PropTypes.string,
  onChangeSortingMethod: React.PropTypes.func,
  userProfile: React.PropTypes.bool,
};

export default FeedBox;


================================================
FILE: frontend/Components/FeedBox/styles.css
================================================
@value borderColor, backShade, secondaryFontColor, primaryFontColor from 'SharedStyles/globalStyles.css';

/**
 * FeedBox styles
 */
.container {
  margin-top: 20px;
  border: 1px solid borderColor;

  &:first-child { margin-top: 0px; }
}

.header {
  border-bottom: 1px solid borderColor;
  height: 30px;
  display: flex;
  flex-flow: row nowrap;
  align-items: center;
  justify-content: space-between;
  background-color: backShade;
}

.title {
  padding-left: 10px;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}

.sortList {
  & .sort {
    color: secondaryFontColor;
    display: inline-block;
    height: 30px;
    line-height: 30px;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    padding: 0px 10px;
    cursor: pointer;
    user-select: none;

    &.sortActive { color: primaryFontColor; }
  }
}

.posts {
  display: flex;
  flex-direction: column;
}

.loading {
  font-size: 12px;
  letter-spacing: 0.5px;
  color: secondaryFontColor;
  padding: 20px 0px;
  text-align: center;
}


================================================
FILE: frontend/Components/Footer/index.js
================================================
import React, { Component } from 'react';
import classnames from 'classnames';

import styles from './styles';
import appLayout from 'SharedStyles/appLayout.css';

class Footer extends Component {
  render() {
    return (
      <div className={classnames(appLayout.constraintWidth, styles.contentArea)}>
        {/* Copyright? Who cares? :-) */}
      </div>
    );
  }
}


Footer.defaultProps = {
};

Footer.propTypes = {
};

export default Footer;


================================================
FILE: frontend/Components/Footer/styles.css
================================================
@value smallBP, mediumBP, largeBP from 'SharedStyles/globalStyles';

/**
 * Footer styles
 */
.contentArea {
  margin-top: 40px;
  margin-bottom: 40px;
  text-align: center;
}


================================================
FILE: frontend/Components/Header/Logo/index.js
================================================
import React, { Component } from 'react';
import styles from './styles';

const Logo = () => {
  return (
    <div className={styles.logoContainer}>
      <div className={styles.logo}>
        <svg viewBox="0 0 100 100"xmlns="http://www.w3.org/2000/svg">
          <g id="Group" stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
            <path d="M51.1388842,81.5721454 L69.5667388,100 L92.0066458,100 C96.4114635,100 100,96.4212534 100,92.0066458 L100,7.99335421 C100,3.58853654 96.4212534,0 92.0066458,0 L7.99335421,0 C3.58853654,0 0,3.57874658 0,7.99335421 L0,92.0066458 C0,96.4114635 3.57874658,100 7.99335421,100 L10.5882353,100 L10.5882353,44.7058824 C10.7474244,24.5366987 27.1464843,8.23529412 47.3529412,8.23529412 C67.6575276,8.23529412 84.1176471,24.6954136 84.1176471,45 C84.1176471,64.0263195 69.6647717,79.676989 51.1388842,81.5721454 Z M24.2232146,73.5788183 L24.1176471,73.6843859 L50.4332612,100 L24.1176471,100 L24.1176471,73.4929507 C24.1527823,73.521637 24.1879715,73.5502596 24.2232146,73.5788183 Z" id="Combined-Shape" fill="#F1C40F"></path>
            <circle id="Oval" fill="#F1C40F" cx="47.6470588" cy="45.2941176" r="23.5294118"></circle>
          </g>
        </svg>
      </div>
      <div className={styles.logoTitle}>ReForum</div>
    </div>
  );
};

export default Logo;


================================================
FILE: frontend/Components/Header/Logo/styles.css
================================================
/* logo */
.logoContainer {
  display: flex;
  flex-flow: row nowrap;
  align-items: center;
}

.logo {
  width: 40px;
  height: 40px;
}

.logoTitle {
  margin-left: 10px;
  font-size: 20px;
  font-weight: 700;
}


================================================
FILE: frontend/Components/Header/NavigationBar/index.js
================================================
import React, { Component } from 'react';
import { Link, IndexLink } from 'react-router';
import classnames from 'classnames';
import _ from 'lodash';
import styles from './styles';

class NavigationBar extends Component {
  render() {
    const {
      navigationLinks,
    } = this.props;

    if (navigationLinks) {
      return (
        <ul className={styles.navigationBar}>
          { navigationLinks.map(link => {
            if (link.id === 0) {
              return (
                <li key={_.uniqueId('navLink_')}>
                  <IndexLink
                    className={styles.links}
                    activeClassName={styles.linkActive}
                    to='/'
                  >
                    Home
                  </IndexLink>
                </li>
              );
            }

            return (
              <li key={_.uniqueId('navLink_')}>
                <Link
                  className={styles.links}
                  activeClassName={styles.linkActive}
                  to={link.link}
                >
                  {link.name}
                </Link>
              </li>
            );
          }) }
        </ul>
      );
    }

    return null;
  }
}

NavigationBar.defaultProps = {
  navigationLinks: [
    {
      id: 0,
      name: 'General',
      link: '/',
    },
  ],
};

NavigationBar.propTypes = {
  navigationLinks: React.PropTypes.array,
};

export default NavigationBar;


================================================
FILE: frontend/Components/Header/NavigationBar/styles.css
================================================
@value primaryFontColor, secondaryFontColor, borderColor from 'SharedStyles/globalStyles.css';
@value smallBP from 'SharedStyles/globalStyles.css';

/**
 * NavigationBar styles
 */

.navigationBar {
  border-top: 1px solid borderColor;
  border-bottom: 1px solid borderColor;
  list-style-type: none;
  display: flex;
  margin: 0px;
  padding: 0px;

  @media smallBP { padding: 0px 10px; }
}

.links {
  display: block;
  padding: 0px 15px;
  height: 38px;
  line-height: 38px;
  text-decoration: none;
  font-size: 14px;
  color: secondaryFontColor;
  user-select: none;
  transition: color 0.3s;
}

.links:hover { color: primaryFontColor; }

.linkActive {
  color: primaryFontColor;
  background-color: #f9f9f9;
}


================================================
FILE: frontend/Components/Header/UserMenu/index.js
================================================
import React, { Component } from 'react';
import { Link } from 'react-router';
import classnames from 'classnames';
import onClickOutside from 'react-onclickoutside';
import styles from './styles';

import Button from 'Components/Button';

class UserMenu extends Component {
  constructor(props) {
    super(props);
    this.state = { activeSubMenu: false };
    this.toggleSubMenu = this.toggleSubMenu.bind(this);
  }

  handleClickOutside() {
    this.setState({ activeSubMenu: false });
  }

  toggleSubMenu() {
    this.setState((prevState) => {
      return { activeSubMenu: !prevState.activeSubMenu };
    });
  }

  renderSubMenu() {
    const { activeSubMenu } = this.state;
    const {
      signedIn,
      gitHandler,
    } = this.props;

    if (activeSubMenu) {
      return (
        <div className={styles.subMenu}>
          <Button className={styles.subMenuClose} onClick={this.toggleSubMenu} alwaysActive>
            <i className={classnames('fa fa-close')}></i>
          </Button>

          { !signedIn && <a className={styles.signInLink} href={'/api/user/authViaGitHub'}>
            <Button className={styles.gitLoginBtn} alwaysActive>
              <i className={classnames('fa fa-github-alt', styles.subMenuOcto)}></i>
              <span className={styles.btnLabel}>With GitHub</span>
            </Button>
          </a> }

          { signedIn && <span onClick={this.toggleSubMenu}><Link className={styles.subMenuItem} to={`/user/${gitHandler}`}>My Profile</Link></span> }
          {/* { signedIn && <a className={styles.subMenuItem} href={'#'}>Settings</a> } */}
          { signedIn && <a className={styles.subMenuItem} href={'/api/user/signout'}>Sign Out</a> }
        </div>
      );
    }

    return null;
  }

  render() {
    const {
      signedIn,
      userName,
      avatar,
      signOutAction,
    } = this.props;

    if (signedIn) {
      return (
        <div style={{ position: 'relative' }}>
          <div className={styles.container} onClick={this.toggleSubMenu}>
            <img className={styles.userAvatar} src={avatar} alt={`${userName} Avatar`} />
            <span className={styles.title}>{userName}</span>
          </div>
          {this.renderSubMenu()}
        </div>
      );
    }

    return (
      <div className={styles.container}>
        <Button
          alwaysActive
          className={classnames(styles.signInBtn, styles.title)}
          onClick={this.toggleSubMenu}
        >
          Sign Up / Sign In
        </Button>

        {this.renderSubMenu()}
      </div>
    );
  }
}

UserMenu.defaultProps = {
  signedIn: false,
  userName: '',
  gitHandler: '',
  avatar: '',
};

UserMenu.propTypes = {
  signedIn: React.PropTypes.bool.isRequired,
  userName: React.PropTypes.string,
  gitHandler: React.PropTypes.string,
  avatar: React.PropTypes.string,
};

export default onClickOutside(UserMenu);


================================================
FILE: frontend/Components/Header/UserMenu/styles.css
================================================
@value primaryFontColor, secondaryFontColor, borderColor from 'SharedStyles/globalStyles.css';
@value smallBP, mediumBP, largeBP from 'SharedStyles/globalStyles.css';

/**
 * UserMenu styles
 */
.container {
  cursor: pointer;
  position: relative;
  display: flex;
  align-items: center;
  flex-flow: row nowrap;
}

.userAvatar {
  width: 40px;
  height: 40px;
  border-radius: 50px;
}

.title {
  margin-left: 15px;
  font-size: 14px;
  font-weight: 700;
}

.signInLink {
  color: primaryFontColor;
  text-decoration: none;
}

.signInBtn {
  padding-left: 0px;
  padding-right: 0px;
  height: 40px;
  display: flex;
  align-items: center;
}

.subMenu {
  z-index: 1;
  padding: 10px 0px;
  position: absolute;
  top: 42px;
  right: 0px;
  width: 240px;

  background-color: white;
  border: 1px solid borderColor;

  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  @media smallBP {
    position: fixed;
    top: 0;
    left: 0;
    height: 100%;
    width: 100%;
    background-color: rgba(255,255,255, 0.8);
    border: none;
  }
}

.subMenuItem {
  width: 100%;
  color: secondaryFontColor;
  text-decoration: none;
  text-align: center;
  line-height: 32px;

  &:hover { color: primaryFontColor; }

  @media smallBP {
    color: primaryFontColor;
    line-height: 42px;
  }
}

.subMenuClose {
  position: absolute;
  top: 10px;
  right: 10px;

  @media mediumBP { display: none; }
  @media largeBP { display: none; }
}

.gitLoginBtn {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 15px 0px;
}

.subMenuOcto {
  font-size: 24px;
  margin-right: 15px;
}


================================================
FILE: frontend/Components/NewDiscussion/PinButton/index.js
================================================
import React, { Component } from 'react';
import classnames from 'classnames';
import styles from './styles';

import Button from 'Components/Button';

class PinButton extends Component {
  constructor(props) {
    super(props);
    this.state = { value: false };
  }

  componentWillReceiveProps(nextProps) {
    const { value } = nextProps;
    this.setState({ value });
  }

  updateValue(value) {
    this.props.onChange(value);
    this.setState({ value });
  }

  render() {
    const { value } = this.state;

    return (
      <div className={styles.container}>
        <div className={styles.label}>Is it a pinned discussion?</div>

        <div className={styles.btnContainer}>
          <Button
            alwaysActive={value ? true : false}
            onClick={() => { this.updateValue(true); }}
          >
            Yes
          </Button>

          <Button
            alwaysActive={!value ? true : false}
            onClick={() => { this.updateValue(false); }}
          >
            No
          </Button>
        </div>

      </div>
    );
  }
}

PinButton.defaultProps = {
  onChange: (val) => {},
  value: false,
};

PinButton.propTypes = {
  onChange: React.PropTypes.func,
  value: React.PropTypes.bool,
};

export default PinButton;


================================================
FILE: frontend/Components/NewDiscussion/PinButton/styles.css
================================================
@value primaryFontColor, secondaryFontColor, borderColor from 'SharedStyles/globalStyles.css';
@value smallBP from 'SharedStyles/globalStyles.css';

.container {
  display: flex;
  align-items: center;
  margin: 5px 0px 0px 10px;
}

.label {
  margin-right: 10px;
}

.btnContainer {

}

.button {
  padding: 10px 20px;
  border: 1px solid borderColor;
}


================================================
FILE: frontend/Components/NewDiscussion/TagsInput/index.js
================================================
import React, { Component } from 'react';
import classnames from 'classnames';
import _ from 'lodash';
import styles from './styles';

import Button from 'Components/Button';
import Tag from 'Components/Tag';

class TagsInput extends Component {
  constructor(props) {
    super(props);

    this.state = {
      errorMsg: null,
      tags: [],
      tagName: '',
    };
  }

  componentWillReceiveProps(nextProps) {
    const { value } = nextProps;
    this.setState({ tags: value, errorMsg: null });
  }

  validateTag(tagName) {
    const regex = /^[a-z0-9.\-_$@*!]{4,20}$/;
    return regex.test(tagName);
  }

  sameTag(tagName) {
    const { tags } = this.state;
    let matched = false;
    tags.map((tag) => {
      if (tag === tagName) matched = true;
    });
    return matched;
  }

  addTag() {
    const {
      tagName,
      tags,
      errorMsg,
    } = this.state;

    if (this.validateTag(tagName)) {
      if (!this.sameTag(tagName)) {
        const newTags = tags.concat(tagName);
        this.setState({
          tags: newTags,
          errorMsg: null,
          tagName: '',
        });
        this.props.onChange(newTags);
      } else {
        this.setState({ errorMsg: 'Same tag!!!' });
      }
    } else {
      this.setState({ errorMsg: 'Tags can only contain small letters and numbers. No space or special characters please. Min 4 and max 20 chars.' });
    }
  }

  removeTag(position) {
    const { tags } = this.state;
    const newTags = [...tags.slice(0, position), ...tags.slice(position + 1, tags.length)];
    this.setState({ tags: newTags });
    this.props.onChange(newTags);
  }

  renderTags() {
    const { tags } = this.state;

    return tags.map((tag, i) => {
      return (
        <Tag
          name={tag}
          key={tag}
          withRemove
          removeAction={() => { this.removeTag(i); }}
        />
      );
    });
  }

  renderInput() {
    const {
      tagName,
      tags,
    } = this.state;
    const { maxTagCount } = this.props;

    if ( tags.length < maxTagCount ) {
      return (
        <div className={styles.inputContainer}>
          <input
            className={styles.tagInput}
            placeholder={'tag name...'}
            value={tagName}
            onChange={(e) => { this.setState({ tagName: e.target.value }); }}
          />
          <Button
            className={styles.addButton}
            onClick={() => { this.addTag(); }}
          >
            <i className={classnames('fa fa-plus-circle')}></i>
          </Button>
        </div>
      );
    }

    return null;
  }

  render() {
    const {
      errorMsg,
      tagName,
      tags,
    } = this.state;

    const { maxTagCount } = this.props;

    return (
      <div className={styles.container}>
        <div className={styles.tagContainer}>
          <div className={styles.label}>Tags :</div>
          { this.renderTags() }
          { this.renderInput() }
        </div>
        { errorMsg && <div className={styles.errorMsg}>{errorMsg}</div> }
      </div>
    );
  }
}

TagsInput.defaultProps = {
  value: [],
  maxTagCount: 3,
  onChange: (tags) => {},
};

TagsInput.propTypes = {
  value: React.PropTypes.array,
  maxTagCount: React.PropTypes.number,
  onChange: React.PropTypes.func,
};

export default TagsInput;


================================================
FILE: frontend/Components/NewDiscussion/TagsInput/styles.css
================================================
@value primaryFontColor, secondaryFontColor, borderColor from 'SharedStyles/globalStyles.css';
@value smallBP from 'SharedStyles/globalStyles.css';

.container {
  margin: 0px 0px 0px 10px;
}

.tagContainer {
  height: 36px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}

.label {

}

.inputContainer {
  display: flex;
  align-items: center;
}

.tagInput {
  width: 100px;
  padding: 10px 10px;
  border: none;
  outline: none;
  background-color: none;
}

.addButton {
  padding: 5px 10px;
  font-size: 20px;
}

.errorMsg {
  font-weight: bold;
  margin-top: 5px;
  color: rgba(231, 76, 60,1.0);
  font-size: 12px;
  line-height: 14px;
}


================================================
FILE: frontend/Components/RichEditor/BlockStyleControls.js
================================================
import React, { Component } from 'react';
import classnames from 'classnames';
import styles from './styles.css';

import Button from 'Components/Button';
import StyleButton from './StyleButton';

class BlockStyleControls extends Component {
  render() {
    const {
      onToggle,
      editorState,
      type,
    } = this.props;

    const blockTypes = [
      // {label: 'H1', style: 'header-one'},
      // {label: 'H2', style: 'header-two'},
      {label: 'H3', style: 'header-three'},
      {label: 'H4', style: 'header-four'},
      {label: 'H5', style: 'header-five'},
      // {label: 'H6', style: 'header-six'},
      {label: 'Blockquote', style: 'blockquote'},
      // {label: 'UL', style: 'unordered-list-item'},
      // {label: 'OL', style: 'ordered-list-item'},
      {label: 'Code Block', style: 'code-block'},
    ];

    const selection = editorState.getSelection();
    const blockType = editorState
      .getCurrentContent()
      .getBlockForKey(selection.getStartKey())
      .getType();

    return (
      <div className={styles.controls}>
        { blockTypes.map((eachType) =>
          <StyleButton
            key={eachType.label}
            onToggle={onToggle}
            active={eachType.style === blockType}
            label={eachType.label}
            style={eachType.style}
          />
        ) }
      </div>
    );
  }
}

BlockStyleControls.propTypes = {
  onToggle: React.PropTypes.func.isRequired,
  editorState: React.PropTypes.any.isRequired,
  type: React.PropTypes.oneOf(['newDiscussion', 'newOpinion']),
};

export default BlockStyleControls;


================================================
FILE: frontend/Components/RichEditor/InlineStyleControls.js
================================================
import React, { Component } from 'react';
import classnames from 'classnames';
import styles from './styles.css';

import Button from 'Components/Button';
import StyleButton from './StyleButton';

class InlineStyleControls extends Component {
  render() {
    const {
      onToggle,
      editorState,
    } = this.props;

    const inlineStyles = [
      {label: 'Bold', style: 'BOLD'},
      {label: 'Italic', style: 'ITALIC'},
      // {label: 'Underline', style: 'UNDERLINE'},
      {label: 'Monospace', style: 'CODE'},
    ];

    const currentStyle = editorState.getCurrentInlineStyle();

    return (
      <div className={styles.controls}>
        { inlineStyles.map((eachType) =>
          <StyleButton
            key={eachType.label}
            onToggle={onToggle}
            active={currentStyle.has(eachType.style)}
            label={eachType.label}
            style={eachType.style}
          />
        ) }
      </div>
    );
  }
}

InlineStyleControls.propTypes = {
  onToggle: React.PropTypes.func.isRequired,
  editorState: React.PropTypes.any.isRequired,
  type: React.PropTypes.oneOf(['newDiscussion', 'newOpinion']),
};

export default InlineStyleControls;


================================================
FILE: frontend/Components/RichEditor/StyleButton.js
================================================
import React, { Component } from 'react';
import classnames from 'classnames';
import styles from './styles.css';

class StyleButton extends React.Component {
  constructor() {
    super();
    this.onToggle = (e) => {
      e.preventDefault();
      this.props.onToggle(this.props.style);
    };
  }

  render() {
    let className = `${styles.controlButton}`;
    if (this.props.active) {
      className += ` ${styles.controlButtonActive}`;
    }

    return (
      <div className={className} onMouseDown={this.onToggle}>
        {this.props.label}
      </div>
    );
  }
}

StyleButton.propTypes = {
  onToggle: React.PropTypes.func.isRequired,
  active: React.PropTypes.any.isRequired,
  label: React.PropTypes.string.isRequired,
  style: React.PropTypes.string.isRequired,
};

export default StyleButton;


================================================
FILE: frontend/Components/RichEditor/index.js
================================================
import React, { Component } from 'react';
import {
  Editor,
  EditorState,
  ContentState,
  RichUtils,
  convertToRaw,
  convertFromRaw,
} from 'draft-js';
import classnames from 'classnames';
import styles from './styles';

import Button from 'Components/Button';
import BlockStyleControls from './BlockStyleControls';
import InlineStyleControls from './InlineStyleControls';

class RichEditor extends Component {
  constructor(props) {
    super(props);
    this.state = {
      editorState: EditorState.createEmpty(),
    };

    this.focus = () => this.refs.editor.focus();
    this.onEditorStateChange = this.onEditorStateChange.bind(this);
    this.handleKeyCommand = this.handleKeyCommand.bind(this);
    this.onTab = this.onTab.bind(this);
    this.toggleBlockType = this.toggleBlockType.bind(this);
    this.toggleInlineStyle = this.toggleInlineStyle.bind(this);
  }

  componentDidMount() {
    const { value } = this.props;
    if (value) {
      const contentState = convertFromRaw(JSON.parse(value));
      const editorState = EditorState.createWithContent(contentState);
      this.setState({ editorState });
    }
  }

  onEditorStateChange(editorState) {
    const { onChange } = this.props;
    this.setState({ editorState });

    // trigger on change converting the ContentState to raw object
    onChange(JSON.stringify(convertToRaw(editorState.getCurrentContent())));
  }

  handleKeyCommand(command) {
    const newState = RichUtils.handleKeyCommand(this.state.editorState, command);
    if (newState) {
      this.onEditorStateChange(newState);
      return true;
    }
    return false;
  }

  onTab(event) {
    const maxDepth = 4;
    this.onEditorStateChange(RichUtils.onTab(event, this.state.editorState, maxDepth));
  }

  toggleBlockType(blockType) {
    this.onEditorStateChange(
      RichUtils.toggleBlockType(
        this.state.editorState,
        blockType
      )
    );
  }

  toggleInlineStyle(inlineStyle) {
    this.onEditorStateChange(
      RichUtils.toggleInlineStyle(
        this.state.editorState,
        inlineStyle
      )
    );
  }

  customBlockStyles(contentBlock) {
    const type = contentBlock.getType();
    if (type === 'blockquote') return styles.editorBlockquoteStyle;
    if (type === 'code-block') return styles.editorCodeStyle;
    if (type === 'header-one') return styles.editorH1Style;
    if (type === 'header-two') return styles.editorH2Style;
    if (type === 'header-three') return styles.editorH3Style;
  }

  render() {
    const {
      type,
      onSave,
      readOnly,
    } = this.props;

    // styling for inline styles
    const inlineStyleMap = {
      'CODE': {
        color: '#e74c3c',
        backgroundColor: '#f9f9f9',
        border: '1px solid #e8e8e8',
        fontFamily: 'monospace',
        padding: '2px 5px',
        margin: '0px 5px',
      },
    };

    let saveButtonLabel = '';
    if (type === 'newOpinion') saveButtonLabel = 'Reply';
    if (type === 'newDiscussion') saveButtonLabel = 'Post Discussion';

    let placeholder = '';
    if (type === 'newOpinion') placeholder = 'Your opinion...';
    if (type === 'newDiscussion') placeholder = 'Discussion summary...';

    return (
      <div className={classnames(styles.container, readOnly && styles.readOnlyContainer)}>
        { !readOnly && <div className={styles.controlsContainer}>
          <InlineStyleControls
            type={type}
            editorState={this.state.editorState}
            onToggle={this.toggleInlineStyle}
          />
          <BlockStyleControls
            type={type}
            editorState={this.state.editorState}
            onToggle={this.toggleBlockType}
          />
        </div> }

        <div
          className={classnames(
            styles.editorContainer,
            !readOnly && styles[type],
            readOnly && styles.readOnlyEditorContainer
          )}
          onClick={this.focus}
        >
          <Editor
            customStyleMap={inlineStyleMap}
            blockStyleFn={this.customBlockStyles}
            readOnly={readOnly}
            editorState={this.state.editorState}
            onChange={this.onEditorStateChange}
            placeholder={placeholder}
            handleKeyCommand={this.handleKeyCommand}
            onTab={this.onTab}
            ref='editor'
          />
        </div>

        { !readOnly && <Button noUppercase style={{ alignSelf: 'center' }} onClick={onSave}>
          {saveButtonLabel}
        </Button> }
      </div>
    );
  }
}

RichEditor.defaultProps = {
  readOnly: false,
  value: null,
  type: 'newDiscussion',
  onChange: () => { },
  onSave: () => { },
};

RichEditor.propTypes = {
  readOnly: React.PropTypes.bool,
  value: React.PropTypes.any,
  type: React.PropTypes.oneOf(['newDiscussion', 'newOpinion']),
  onChange: React.PropTypes.func,
  onSave: React.PropTypes.func,
};

export default RichEditor;


================================================
FILE: frontend/Components/RichEditor/styles.css
================================================
@value borderColor, secondaryFontColor, primaryFontColor, backShade from 'SharedStyles/globalStyles.css';
@value smallBP from 'SharedStyles/globalStyles';

/**
 * RichEditor styles
 */

.container {
  margin-top: 10px;
  margin-bottom: 20px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  border: 1px solid borderColor;
}

.readOnlyContainer {
  border: none;
}

.editorContainer {
  padding: 10px 10px;
  width: 100%;
  min-height: 60px;
  border-bottom: 1px solid borderColor;
  cursor: text;

  & :global .public-DraftEditorPlaceholder-root {
    position: absolute;
    color: secondaryFontColor;
  }
}

.readOnlyEditorContainer {
  padding: 0px;
  border-bottom: none;
}

.buttonClass {
  margin-top: 20px;
}

.newDiscussion { min-height: 300px; }


/**
 * Controls
 */
.controlsContainer {
  width: 100%;
  display: flex;
  flex-direction: row;
  border-bottom: 1px solid borderColor;
  @media smallBP { flex-direction: column-reverse; }
}

.controls {
  display: flex;
}

.controlButton {
  padding: 10px 10px;
  user-select: none;
  cursor: pointer;

  text-transform: uppercase;
  color: secondaryFontColor;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.5px;

  transition: color 0.3s;

  &:hover { color: primaryFontColor; }

  @media smallBP { padding: 5px 10px; }
}

.controlButtonActive {
  color: primaryFontColor;
}

/**
 * editor text styles
 */

.editorBlockquoteStyle {
  display: inline-block;
  position: relative;
  margin: 10px 0px 10px 20px;
  padding: 10px 20px;
  background-color: backShade;
  font-style: italic;
}

.editorBlockquoteStyle::before {
  content: '';
  position: absolute;
  height: 100%;
  width: 5px;
  left: 0px;
  top: 0px;
  background-color: borderColor;
}

.editorCodeStyle {
  margin: 0px;
  padding: 0px;
  line-height: 18px;
  overflow-x: hidden;
}

:global .public-DraftStyleDefault-pre {
  margin: 10px 0px;
  padding: 5px 5px;
  background-color: backShade;
  border: 1px solid borderColor;
}

.editorH1Style,
.editorH2Style,
.editorH3Style { color: #4c4c4c; }

.editorH1Style { font-size: 1.6em; }
.editorH2Style { font-size: 1.3em; }
.editorH3Style { font-size: 1.1em; }


================================================
FILE: frontend/Components/SideBar/index.js
================================================
import React, { Component } from 'react';
import { Link } from 'react-router';
import styles from './styles';

import Button from 'Components/Button';

class SideBar extends Component {
  render() {
    const {
      currentForum,
    } = this.props;

    return (
      <div className={styles.sidebarContainer}>
        <Link to={`/${currentForum}/new_discussion`}>
          <Button type='outline' fullWidth noUppercase>
            New Discussion
          </Button>
        </Link>
      </div>
    );
  }
}


SideBar.defaultProps = {
  currentForum: 'general',
};

SideBar.propTypes = {
  currentForum: React.PropTypes.string,
};

export default SideBar;


================================================
FILE: frontend/Components/SideBar/styles.css
================================================
@value smallBP, mediumBP, largeBP from 'SharedStyles/globalStyles';

/**
 * SideBar component styles
 */
.sidebarContainer {
  width: 250px;
  margin-left: 10px;
}


================================================
FILE: frontend/Components/SingleDiscussion/Discussion/index.js
================================================
import _ from 'lodash';
import React, { Component } from 'react';
import { Link } from 'react-router';
import moment from 'moment';
import classnames from 'classnames';
import styles from './styles.css';

import PlaceholderImage from 'SharedStyles/placeholder.jpg';
import Button from 'Components/Button';
import Tag from 'Components/Tag';
import RichEditor from 'Components/RichEditor';

class Discussion extends Component {
  render() {
    const {
      id,
      userAvatar,
      userName,
      userGitHandler,
      discTitle,
      discDate,
      discContent,
      tags,
      favoriteCount,
      favoriteAction,
      userFavorited,
      toggleingFavorite,
      allowDelete,
      deletingDiscussion,
      deleteAction,
    } = this.props;

    let dateDisplay = moment(discDate);
    dateDisplay = dateDisplay.from(moment());

    let favCount = '';
    if (toggleingFavorite) favCount = 'Toggling Favorite...';
    else if (userFavorited) favCount = `Favorited (${favoriteCount})`;
    else if (favoriteCount === 0) favCount = 'Make favorite';
    else if (favoriteCount === 1) favCount = '1 favorite';
    else favCount = `${favoriteCount} favorites`;

    return (
      <div className={styles.container}>

        <div className={styles.infoContainer}>
          <img className={styles.avatar} src={userAvatar} />
          <div className={styles.columnOnSmallBP}>
            <div className={styles.userInfo}>
              <Link to={`/user/${userGitHandler}`} className={styles.name}>{userName || userGitHandler}</Link>
              <a href={`https://www.github.com/${userGitHandler}`} target="_blank" className={styles.gitHandler}>
                <i className={classnames('fa fa-github-alt', styles.gitIcon)}></i>
                <span>{userGitHandler}</span>
              </a>
            </div>
            <div className={styles.dateInfo}>{dateDisplay}</div>
          </div>
        </div>

        <div className={styles.discTitle}>{discTitle}</div>
        <div className={styles.discContent}>
          <RichEditor
            readOnly={true}
            value={discContent}
          />
        </div>

        <div className={styles.discFooter}>
          <div className={styles.tags}>
            { tags.map(tag => <Tag name={tag} key={_.uniqueId('tag_')} />)}
          </div>
          <Button noUppercase className={styles.favoriteButton} onClick={() => { !toggleingFavorite && favoriteAction(id); }}>
            <i className={classnames(`fa fa-${userFavorited ? 'heart' : 'heart-o'}`)}></i>
            <span>{favCount}</span>
          </Button>

          { allowDelete && <Button noUppercase className={styles.deleteButton} onClick={() => { deleteAction(); }}>
            <i className={classnames('fa fa-trash', styles.trashIcon)}></i>
            <span>Delete</span>
          </Button> }
        </div>

        { deletingDiscussion && <div className={styles.deletingDiscussion}>
          Deleting Discussion...
        </div> }
      </div>
    );
  }
}

Discussion.defaultProps = {
  id: 0,
  userAvatar: PlaceholderImage,
  userName: 'User name',
  userGitHandler: 'github',
  discTitle: 'Default Discussion Title',
  discDate: 'a day ago',
  discContent: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
  tags: [ 'react', 'redux', 'webkit' ],
  favoriteCount: 1,
  favoriteAction: () => { },
  userFavorited: false,
  toggleingFavorite: false,
  allowDelete: false,
  deletingDiscussion: false,
  deleteAction: () => { },
};

Discussion.propTypes = {
  id: React.PropTypes.any,
  userAvatar: React.PropTypes.string,
  userName: React.PropTypes.string,
  userGitHandler: React.PropTypes.string,
  discTitle: React.PropTypes.string,
  discDate: React.PropTypes.any,
  discContent: React.PropTypes.any,
  tags: React.PropTypes.array,
  favoriteCount: React.PropTypes.number,
  favoriteAction: React.PropTypes.func,
  userFavorited: React.PropTypes.bool,
  toggleingFavorite: React.PropTypes.bool,
  allowDelete: React.PropTypes.bool,
  deletingDiscussion: React.PropTypes.bool,
  deleteAction: React.PropTypes.func,
};

export default Discussion;


================================================
FILE: frontend/Components/SingleDiscussion/Discussion/styles.css
================================================
@value borderColor, secondaryFontColor, primaryFontColor from 'SharedStyles/globalStyles.css';
@value smallBP from 'SharedStyles/globalStyles.css';

/**
 * Discussion styles
 */

.container {
  padding: 20px 10px 0px 10px;
  position: relative;
}

.infoContainer {
  display: flex;
  align-items: center;

  & .avatar {
    width: 42px;
    height: 42px;
    border-radius: 50px;
  }

  & .columnOnSmallBP {
    flex: 1;
    display: flex;
    @media smallBP { flex-direction: column; }
  }

  & .userInfo {
    margin-left: 10px;
    flex: 1;
    display: flex;

    & .name {
      font-weight: 700;
      color: primaryFontColor;
      text-decoration: none;
    }

    & .gitHandler {
      margin-left: 15px;
      text-decoration: none;
      color: secondaryFontColor;
      transition: color 0.3s;
      &:hover { color: primaryFontColor; }

      & .gitIcon { margin-right: 5px; }
    }
  }

  & .dateInfo {
    font-size: 13px;
    color: secondaryFontColor;
    @media smallBP { margin-left: 10px; }
  }
}

.discTitle {
  margin-top: 15px;
  font-size: 24px;
  line-height: 32px;
  font-weight: 400;
}

.discContent {
  margin-top: 10px;
  font-size: 15px;
  line-height: 26px;
}

.discFooter {
  margin-top: 10px;
  display: flex;
  align-items: center;

  @media smallBP {
    flex-direction: column;
    align-items: flex-start;
  }

  & .tags { flex: 1; }

  & .favoriteButton,
  & .deleteButton {
    @media smallBP {
      margin-top: 5px;
      padding-left: 0px;
      padding-right: 0px;
    }
    & i {
      font-size: 20px;
      margin-right: 10px;
    }
  }
}

.deletingDiscussion {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(255, 255, 255, 0.9);

  display: flex;
  justify-content: center;
  align-items: center;
}


================================================
FILE: frontend/Components/SingleDiscussion/Opinion/index.js
================================================
import React, { Component } from 'react';
import { Link } from 'react-router';
import moment from 'moment';
import classnames from 'classnames';
import styles from './styles.css';

import PlaceholderImage from 'SharedStyles/placeholder.jpg';
import Button from 'Components/Button';
import RichEditor from 'Components/RichEditor';

class Opinion extends Component {
  render() {
    const {
      opinionId,
      userAvatar,
      userName,
      userGitHandler,
      opDate,
      opContent,
      userId,
      currentUserId,
      currentUserRole,
      deleteAction,
      deletingOpinion,
    } = this.props;

    let dateDisplay = moment(opDate);
    dateDisplay = dateDisplay.from(moment());

    const allowDelete = (userId === currentUserId) || (currentUserRole === 'admin');

    return (
      <div className={styles.container}>
        <div className={styles.infoContainer}>
          <img className={styles.avatar} src={userAvatar} />
          <div className={styles.userInfo}>
            <Link to={`/user/${userGitHandler}`} className={styles.name}>{userName || userGitHandler}</Link>
            <a href={`https://www.github.com/${userGitHandler}`} target="_blank" className={styles.gitHandler}>
              <i className={classnames('fa fa-github-alt', styles.gitIcon)}></i>
              <span>{userGitHandler}</span>
            </a>
          </div>
          <div className={styles.dateInfo}>{dateDisplay}</div>
          { allowDelete && <Button className={styles.deleteButton} noUppercase onClick={() => { deleteAction(opinionId); }}>
            <i className={classnames('fa fa-trash', styles.trashIcon)}></i>
            <span>Delete</span>
          </Button> }
          {/* <Button noUppercase>Quote</Button> */}
        </div>

        <div className={styles.opContent}>
          <RichEditor
            readOnly
            value={opContent}
          />
        </div>

        { (deletingOpinion === opinionId) && <div className={styles.deletingOpinion}>Deleting Opinion ...</div> }
      </div>
    );
  }
}

Opinion.defaultProps = {
  opinionId: '12345',
  userAvatar: PlaceholderImage,
  userName: 'User name',
  userGitHandler: 'github',
  opDate: 'a day ago',
  opContent: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
  userId: '12345',
  currentUserId: '12345',
  currentUserRole: 'user',
  deleteAction: () => {},
  deletingOpinion: null,
};

Opinion.propTypes = {
  opinionId: React.PropTypes.string,
  userAvatar: React.PropTypes.string,
  userName: React.PropTypes.string,
  userGitHandler: React.PropTypes.string,
  opDate: React.PropTypes.any,
  opContent: React.PropTypes.string,
  userId: React.PropTypes.string,
  currentUserId: React.PropTypes.string,
  currentUserRole: React.PropTypes.string,
  deleteAction: React.PropTypes.func,
  deletingOpinion: React.PropTypes.any,
};

export default Opinion;


================================================
FILE: frontend/Components/SingleDiscussion/Opinion/styles.css
================================================
@value borderColor, secondaryFontColor, primaryFontColor from 'SharedStyles/globalStyles.css';
@value smallBP from 'SharedStyles/globalStyles.css';

/**
 * Opinion styles
 */

.container {
  position: relative;
  margin-bottom: 10px;
  padding: 0px 10px 0px 10px;
  border: 1px solid borderColor;
  /*border-bottom: none;

  &:last-child { border-bottom: 1px solid borderColor; }*/

  & .deletingOpinion {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: rgba(255, 255, 255, 0.9);
  }
}

.infoContainer {
  padding: 5px 0px;
  display: flex;
  align-items: center;
  border-bottom: 1px solid borderColor;

  & .avatar {
    width: 32px;
    height: 32px;
    border-radius: 50px;
  }

  & .userInfo {
    margin-left: 10px;
    flex: 1;
    display: flex;

    & .name {
      font-weight: 700;
      color: primaryFontColor;
      text-decoration: none;
    }

    & .gitHandler {
      margin-left: 15px;
      text-decoration: none;
      color: secondaryFontColor;
      transition: color 0.3s;
      &:hover { color: primaryFontColor; }

      & .gitIcon { margin-right: 5px; }
    }
  }

  & .dateInfo {
    font-size: 13px;
    line-height: 13px;
    color: secondaryFontColor;
    @media smallBP { margin-left: 10px; }
  }

  & .deleteButton {
    padding: 5px 10px;
    font-weight: normal;

    & .trashIcon {
      margin-right: 5px;
      font-size: 16px;
    }
  }
}

.opContent {
  margin-top: 10px;
  margin-bottom: 20px;
  min-height: 60px;
  font-size: 15px;
  line-height: 26px;
}

.commentFooter {
  display: flex;
  align-items: center;

  & .favoriteButton {
    padding: 10px 10px;
    display: flex;
    align-items: center;
    color: secondaryFontColor;
    cursor: pointer;
    user-select: none;
    transition: color 0.3s;
    &:hover { color: primaryFontColor; }

    & i {
      font-size: 20px;
      margin-right: 10px;
    }
  }
}


================================================
FILE: frontend/Components/SingleDiscussion/ReplyBox/index.js
================================================
import React, { Component } from 'react';
import styles from './styles.css';

import RichEditor from 'Components/RichEditor';

class ReplyBox extends Component {
  render() {
    const {
      posting,
      onSubmit,
      onChange,
    } = this.props;

    if (posting) return <div className={styles.loadingWrapper}>Posting your opinion...</div>;

    return (
      <RichEditor
        type="newOpinion"
        onSave={onSubmit}
        onChange={onChange}
      />
    );
  }
}

ReplyBox.defaultProps = {
  posting: false,
  onSubmit: () => { },
  onChange: (value) => { },
};

ReplyBox.propTypes = {
  posting: React.PropTypes.bool,
  onSubmit: React.PropTypes.func,
  onChange: React.PropTypes.func,
};

export default ReplyBox;


================================================
FILE: frontend/Components/SingleDiscussion/ReplyBox/styles.css
================================================
@value primaryFontColor, secondaryFontColor from 'SharedStyles/globalStyles.css';

/**
 * Reply styles
 */

.loadingWrapper {
  font-size: 12px;
  font-weight: bold;
  text-align: center;
  padding: 30px 0px;
  color: secondaryFontColor;
}


================================================
FILE: frontend/Components/Tag/index.js
================================================
import React, { Component } from 'react';
import classnames from 'classnames';
import styles from './styles';

import Button from 'Components/Button';

class Tag extends Component {
  render() {
    const {
      name,
      withRemove,
      removeAction,
    } = this.props;

    return (
      <div className={classnames(styles.tag, withRemove && styles.tagWithRemove)}>
        {name}
        { withRemove &&
          <Button
            onClick={removeAction}
            className={styles.removeButton}
          >
            <i className={'fa fa-close'}></i>
          </Button>
        }
      </div>
    );
  }
}

Tag.defaultProps = {
  name: '',
  withRemove: false,
  removeAction: () => {},
};

Tag.propTypes = {
  name: React.PropTypes.string.isRequired,
  withRemove: React.PropTypes.bool,
  removeAction: React.PropTypes.func,
};

export default Tag;


================================================
FILE: frontend/Components/Tag/styles.css
================================================
@value secondaryFontColor, backShade from 'SharedStyles/globalStyles';

/**
 * Tag styles
 */

.tag {
  display: inline-block;
  height: 25px;
  margin-left: 10px;
  padding: 0 10px;
  background-color: backShade;
  color: secondaryFontColor;
  font-size: 12px;
  line-height: 25px;

  user-select: none;

  &:first-child { margin-left: 0; }
}

.tagWithRemove {
  display: flex;
  align-items: center;
  padding-right: 0px;
}

.removeButton {
  padding: 4px 10px;
}


================================================
FILE: frontend/Components/UserProfile/Profile/index.js
================================================
import React, { Component } from 'react';
import classnames from 'classnames';
import styles from './styles.css';

class Profile extends Component {
  render() {
    const {
      name,
      gitHandler,
      location,
      avatarUrl,
    } = this.props;

    return (
      <div className={styles.container}>
        <div className={styles.avatarContainer}>
          <img className={styles.avatar} src={avatarUrl} alt={`${name} avatar`} />
        </div>
        <div className={styles.infoContainer}>
          <div className={styles.name}>{ name }</div>
          <div className={styles.gitHandler}><i className={classnames('fa fa-github-alt', styles.gitIcon)}></i> { gitHandler }</div>
          <div className={styles.location}>{ location }</div>
        </div>
      </div>
    );
  }
}

Profile.defaultProps = {
  name: 'Hello World',
  gitHandler: 'helloWorld',
  location: 'Somewhere in the world',
  avatarUrl: 'https://google.com',
};

Profile.propTypes = {
  name: React.PropTypes.string,
  gitHandler: React.PropTypes.string,
  location: React.PropTypes.string,
  avatarUrl: React.PropTypes.string,
};

export default Profile;


================================================
FILE: frontend/Components/UserProfile/Profile/styles.css
================================================
@value primaryFontColor, secondaryFontColor from 'SharedStyles/globalStyles.css';

.container {
  display: flex;
  align-items: center;
}

.avatarContainer {
  & .avatar {
    width: 100px;
    height: 100px;
    border-radius: 10px;
  }
}

.infoContainer {
  margin-left: 20px;
  letter-spacing: 0.5px;

  & .name { font-size: 24px; margin-bottom: 10px; }
  & .gitHandler { font-size: 14px; font-weight: bold; }
  & .location { font-size: 14px; }
}


================================================
FILE: frontend/Containers/AdminHeader/index.js
================================================
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import classnames from 'classnames';

import appLayout from 'SharedStyles/appLayout';
import styles from './styles';

// components for AdminHeader
import UserMenu from 'Components/Header/UserMenu';
import Logo from 'Components/Header/Logo';
import NavigationBar from 'Components/Header/NavigationBar';
import PlaceholderImage from 'SharedStyles/placeholder.jpg';

class AdminHeader extends Component {
  renderNavLinks() {
    return [
      { name: 'Dashboard', link: '/admin' },
    ];
  }

  render() {
    const {
      authenticated,
      name,
      username,
      avatarUrl,
    } = this.props.user;

    return (
      <div className={classnames(appLayout.constraintWidth)}>
        <div className={styles.headerTop}>
          <Logo />
          Welcome Admin
          <UserMenu
            signedIn={authenticated}
            userName={name || username}
            gitHandler={username}
            avatar={avatarUrl}
          />
        </div>
        <NavigationBar
          navigationLinks={this.renderNavLinks()}
        />
      </div>
    );
  }
}

export default connect(
  (state) => { return {
    user: state.user,
    forums: state.app.forums,
  }; }
)(AdminHeader);


================================================
FILE: frontend/Containers/AdminHeader/styles.css
================================================
@value smallBP from 'SharedStyles/globalStyles.css';

/**
 * Header Styles
 */
.headerTop {
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: space-between;

  @media smallBP { padding: 0px 10px; }
}


================================================
FILE: frontend/Containers/Header/index.js
================================================
import React, { Component } from 'react';
import { connect } from 'react-redux';
import classnames from 'classnames';

import appLayout from 'SharedStyles/appLayout';
import styles from './styles';

// components for Header
import UserMenu from 'Components/Header/UserMenu';
import Logo from 'Components/Header/Logo';
import NavigationBar from 'Components/Header/NavigationBar';
import PlaceholderImage from 'SharedStyles/placeholder.jpg';

class Header extends Component {
  renderNavLinks() {
    const { forums } = this.props;

    if (forums) {
      return forums.map((forum) => {
        return {
          id: forum._id,
          name: forum.forum_name,
          link: `/${forum.forum_slug}`,
        };
      });
    }

    return null;
  }

  render() {
    const {
      authenticated,
      name,
      username,
      avatarUrl,
    } = this.props.user;

    return (
      <div className={classnames(appLayout.constraintWidth)}>
        <div className={styles.headerTop}>
          <Logo />
          <UserMenu
            signedIn={authenticated}
            userName={name || username}
            gitHandler={username}
            avatar={avatarUrl}
          />
        </div>
        <NavigationBar
          navigationLinks={this.renderNavLinks()}
        />
      </div>
    );
  }
}

export default connect(
  (state) => { return {
    user: state.user,
    forums: state.app.forums,
  }; }
)(Header);


================================================
FILE: frontend/Containers/Header/styles.css
================================================
@value smallBP from 'SharedStyles/globalStyles.css';

/**
 * Header Styles
 */
.headerTop {
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: space-between;

  @media smallBP { padding: 0px 10px; }
}


================================================
FILE: frontend/SharedStyles/appLayout.css
================================================
@value largeBP, mediumBP, smallBP from 'SharedStyles/globalStyles';

/**
 * application layout styles
 */
.constraintWidth {
  margin: 0px auto;
  min-width: 320px;
  max-width: 1100px;

  @media largeBP { padding: 0px; }
  @media mediumBP { padding: 0px 15px; }
}

.primaryContent { flex: 1; }

.secondaryContent {
  width: 260px;

  @media mediumBP { display: none; }
  @media smallBP { display: none; }
}

.showOnMediumBP {
  display: none;
  @media mediumBP { display: inherit; }
  @media smallBP { display: inherit; }
}


================================================
FILE: frontend/SharedStyles/globalStyles.css
================================================
/**
 * color variables
 */
@value themeColor: #F1C40F;
@value primaryFontColor: #000;
@value secondaryFontColor: #999;
@value borderColor: #e8e8e8;
@value backShade: #f9f9f9;

/**
 * media query breakpoints
 */
@value largeBP: (min-width: 1100px);
@value mediumBP: (min-width: 769px) and (max-width: 1099px);
@value smallBP: (max-width: 768px);

/**
 * global stylings
 */

* { box-sizing: border-box; }

/**
 * typography
 */
body {
  font-size: 14px;
  line-height: 22px;
  font-family: 'Lato', helvetica, arial, sans-serif;
}


================================================
FILE: frontend/Views/AdminDashboard/actions.js
================================================
import {
  GET_ALL_INFO_START,
  GET_ALL_INFO_SUCCESS,
  GET_ALL_INFO_FAILURE,

  CREATE_FORUM,
  CREATE_FORUM_SUCCESS,
  CREATE_FORUM_FAILURE,

  DELETE_FORUM,
  DELETE_FORUM_SUCCESS,
  DELETE_FORUM_FAILURE,
} from './constants';

import {
  getAdminDashboardInfoAPI,
  createForumAPI,
  deleteForumAPI,
} from './api';

/**
 * get all the info needed for dashboard
 * @return {action}
 */
export const getAdminDashboardInfo = () => {
  return (dispatch, getState) => {
    dispatch({ type: GET_ALL_INFO_START });

    getAdminDashboardInfoAPI().then(
      data => dispatch({ type: GET_ALL_INFO_SUCCESS, payload: data.data }),
      error => dispatch({ type: FETCHING_DISCUSSIONS_FAILURE, payload: error })
    );
  };
};

/**
 * create a new forum
 * @param  {Object} forumObj
 * @return {action}
 */
export const createForum = (forumObj) => {
  return (dispatch, getState) => {
    dispatch({ type: CREATE_FORUM });

    // call the create forum api
    createForumAPI(forumObj).then(
      forumData => {
        // get admin info again to refresh the infos
        getAdminDashboardInfoAPI().then(
          data => {
            // data is refreshed
            dispatch({ type: GET_ALL_INFO_SUCCESS, payload: data.data });

            // check if the forum was created
            if (forumData.data.created) { dispatch({ type: CREATE_FORUM_SUCCESS }); }
            else dispatch({ type: CREATE_FORUM_FAILURE });
          },
          error => dispatch({ type: FETCHING_DISCUSSIONS_FAILURE, payload: error })
        );
      },
      error => dispatch({ type: CREATE_FORUM_FAILURE })
    );
  };
};

export const deleteForum = (forumId) => {
  return (dispatch, getState) => {
    dispatch({ type: DELETE_FORUM });

    deleteForumAPI(forumId).then(
      forumData => {
        dispatch({ type: GET_ALL_INFO_START });

        // get admin info again to refresh the infos
        getAdminDashboardInfoAPI().then(
          data => {
            dispatch({ type: GET_ALL_INFO_SUCCESS, payload: data.data });

            // check if th eforum was deleted
            if (forumData.data.deleted) { dispatch({ type: DELETE_FORUM_SUCCESS }); }
            else dispatch({ type: DELETE_FORUM_FAILURE });
          },
          error => dispatch({ type: FETCHING_DISCUSSIONS_FAILURE, payload: error })
        );
      },
      error => dispatch({ type: DELETE_FORUM_FAILURE })
    );
  };
};


================================================
FILE: frontend/Views/AdminDashboard/api.js
================================================
import axios from 'axios';

export const getAdminDashboardInfoAPI = () => {
  return (axios.get('/api/admin/admin_dashboard_info'));
};

export const createForumAPI = (forum_obj) => {
  return (axios.post('/api/admin/create_forum', forum_obj));
};

export const deleteForumAPI = (forum_id) => {
  return (axios.post('/api/admin/delete_forum', { forum_id }));
};


================================================
FILE: frontend/Views/AdminDashboard/constants.js
================================================
export const GET_ALL_INFO_START = 'get_all_info_start';
export const GET_ALL_INFO_SUCCESS = 'get_all_info_success';
export const GET_ALL_INFO_FAILURE = 'get_all_info_failure';

export const CREATE_FORUM = 'create_forum';
export const CREATE_FORUM_SUCCESS = 'create_forum_success';
export const CREATE_FORUM_FAILURE = 'create_forum_failure';

export const DELETE_FORUM = 'delete_forum';
export const DELETE_FORUM_SUCCESS = 'delete_forum_success';
export const DELETE_FORUM_FAILURE = 'delete_forum_failure';


================================================
FILE: frontend/Views/AdminDashboard/index.js
================================================
import React, { Component } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import classnames from 'classnames';
import appLayout from 'SharedStyles/appLayout.css';
import styles from './styles.css';

import {
  getAdminDashboardInfo,
  getForums,
  createForum,
  deleteForum,
} from './actions';
import Counts from 'Components/Dashboard/Counts';
import ForumBox from 'Components/Dashboard/ForumBox';

class Dashboard extends Component {
  componentDidMount() {
    // get information needed for dashboard
    this.props.getAdminDashboardInfo();
  }

  render() {
    const {
      discussionCount,
      opinionCount,
      forumCount,
      userCount,
      forums,
    } = this.props.adminInfo.info;

    const {
      loadingInfo,
      creatingForum,
      creatingForumError,
      deletingForum,
      deletingForumError,
    } = this.props;

    const forumsArray = forums.map((forum) => {
      return { id: forum._id, name: forum.forum_name, slug: forum.forum_slug };
    });

    return (
      <div className={classnames(appLayout.constraintWidth, styles.container)}>
        { loadingInfo && <div className={classnames(styles.loadingMsg)}>
          Loading dashboard info...
        </div> }

        <div className={styles.countsContainer}>
          <Counts label={'Users'} count={userCount} />
          <Counts label={'Discussions'} count={discussionCount} />
          <Counts label={'Opinions'} count={opinionCount} />
          <Counts label={'Forums'} count={forumCount} />
        </div>

        <ForumBox
          forums={forumsArray}
          deletingForum={deletingForum}
          deleteAction={(forumId) => { this.props.deleteForum(forumId); }}
          creatingForum={creatingForum}
          createAction={(forumObj) => { this.props.createForum(forumObj); }}
        />

        { creatingForumError && <div className={styles.errorMsg}>{creatingForumError}</div> }
        { deletingForumError && <div className={styles.errorMsg}>{deletingForumError}</div> }
      </div>
    );
  }
}

export default connect(
  (state) => { return {
    adminInfo: state.adminInfo,
    loadingInfo: state.adminInfo.loadingInfo,
    creatingForum: state.adminInfo.creatingForum,
    creatingForumError: state.adminInfo.creatingForumError,
    deletingForum: state.adminInfo.deletingForum,
    deletingForumError: state.adminInfo.deletingForumError,
  }; },
  (dispatch) => { return {
    getAdminDashboardInfo: () => { dispatch(getAdminDashboardInfo()); },
    getForums: () => { dispatch(getForums()); },
    deleteForum: (forumId) => { dispatch(deleteForum(forumId)); },
    createForum: (forumObj) => { dispatch(createForum(forumObj)); },
  }; }
)(Dashboard);


================================================
FILE: frontend/Views/AdminDashboard/reducers.js
================================================
import {
  GET_ALL_INFO_START,
  GET_ALL_INFO_SUCCESS,
  GET_ALL_INFO_FAILURE,

  CREATE_FORUM,
  CREATE_FORUM_SUCCESS,
  CREATE_FORUM_FAILURE,

  DELETE_FORUM,
  DELETE_FORUM_SUCCESS,
  DELETE_FORUM_FAILURE,
} from './constants';

const initialState = {
  loadingInfo: false,
  info: {
    discussionCount: 0,
    opinionCount: 0,
    forumCount: 0,
    userCount: 0,
    forums: [],
  },
  error: null,

  creatingForum: false,
  creatingForumError: null,

  deletingForum: false,
  deletingForumError: null,
};

export const adminInfoReducer = (state = initialState, action) => {
  switch (action.type) {
    case GET_ALL_INFO_START:
      return Object.assign({}, state, {
        loadingInfo: true,
        error: null,
      });

    case GET_ALL_INFO_SUCCESS:
      return Object.assign({}, state, {
        loadingInfo: false,
        info: action.payload,
        error: null,
      });

    case GET_ALL_INFO_FAILURE:
      return Object.assign({}, state, {
        loadingInfo: false,
        error: 'Something went wrong while loading admin level information.',
      });

    case CREATE_FORUM:
      return Object.assign({}, state, {
        creatingForumError: null,
        creatingForum: true,
      });

    case CREATE_FORUM_SUCCESS:
      return Object.assign({}, state, {
        creatingForum: false,
        creatingForumError: null,
      });

    case CREATE_FORUM_FAILURE:
      return Object.assign({}, state, {
        creatingForum: false,
        creatingForumError: 'Something went wrong while trying to create the forum. Please try again. Also check out if the forum already exists.',
      });

    case DELETE_FORUM:
      return Object.assign({}, state, {
        deletingForum: true,
        deletingForumError: null,
      });

    case DELETE_FORUM_SUCCESS:
      return Object.assign({}, state, {
        deletingForum: false,
        deletingForumError: null,
      });

    case DELETE_FORUM_FAILURE:
      return Object.assign({}, state, {
        deletingForum: false,
        deletingForumError: 'Something went wrong while trying to delete the forum. Please try again later.',
      });

    default:
      return state;
  }
};


================================================
FILE: frontend/Views/AdminDashboard/styles.css
================================================
@value smallBP from 'SharedStyles/globalStyles';

.container {
  margin-top: 20px;
  position: relative;
}

.countsContainer {
  margin-top: 40px;
  margin-bottom: 40px;

  display: flex;
  justify-content: space-around;

  @media smallBP { flex-wrap: wrap; }
}

.loadingMsg {
  margin-top: -30px;
  position: absolute;
  top: 0px;
  left: 0px;
  width: 100%;
  text-align: center;
}

.errorMsg {
  font-weight: bold;
  padding: 10px 0px;
  color: rgba(231, 76, 60,1.0);
  font-size: 12px;
  line-height: 14px;
  text-align: center;
}


================================================
FILE: frontend/Views/ForumFeed/actions.js
================================================
import _ from 'lodash';
import {
  START_FETCHING_DISCUSSIONS,
  STOP_FETCHING_DISCUSSIONS,
  FETCHING_DISCUSSIONS_SUCCESS,
  FETCHING_DISCUSSIONS_FAILURE,

  START_FETCHING_PINNED_DISCUSSIONS,
  STOP_FETCHING_PINNED_DISCUSSIONS,
  FETCHING_PINNED_DISCUSSIONS_SUCCESS,
  FETCHING_PINNED_DISCUSSIONS_FAILURE,

  UPDATE_SORTING_METHOD,
  INVALID_FORUM,
} from './constants';
import {
  fetchDiscussions,
  fetchPinnedDiscussions,
} from './api';

/**
 * find the id for current forum
 * @param  {Object} state   the state object
 * @param  {String} forum   current forum
 * @return {Number}         the forum id
 */
const findForumId = (state, forum) => {
  const { forums } = state.app;
  const forumId = _.find(forums, { forum_slug: forum });

  if (forumId) { return forumId._id; }
  else { return null; }
};

/**
 * action to fetch forum discussions
 * @param  {String}  forum               current forum slug
 * @param  {Boolean} feedChanged         if the feed has been changed, default is false
 * @param  {String}  sortingMethod       define the sorting method, default is 'date'
 * @param  {Boolean} sortingChanged      if user chagned the sorting method
 * @return {thunk}
 */
export const getDiscussions = (forumId, feedChanged=false, sortingChanged=false) => {
  return (dispatch, getState) => {
    const sortingMethod = getState().feed.sortingMethod;

    // show the loading message when user change forum or change sorting method
    if (feedChanged || sortingChanged) dispatch({ type: START_FETCHING_DISCUSSIONS });

    if (!forumId) {
      dispatch({ type: INVALID_FORUM });
    }
    else {
      // start fetching discussions
      fetchDiscussions(forumId, sortingMethod).then(
        data => dispatch({ type: FETCHING_DISCUSSIONS_SUCCESS, payload: data.data }),
        error => dispatch({ type: FETCHING_DISCUSSIONS_FAILURE })
      );
    }
  };
};

/**
 * action to fetch forum pinned discussions
 * @param  {String}  forum                current forum
 * @param  {Boolean} [feedChanged=false]  if the feed has been changed
 * @return {thunk}
 */
export const getPinnedDiscussions = (forumId, feedChanged) => {
  return (dispatch, getState) => {
    // show the loading message when user change forum
    if (feedChanged) dispatch({ type: START_FETCHING_PINNED_DISCUSSIONS });;

    if (!forumId) {
      dispatch({ type: INVALID_FORUM });
    }
    else {
      // start fetching pinned discussions
      return fetchPinnedDiscussions(forumId).then(
        data => dispatch({ type: FETCHING_PINNED_DISCUSSIONS_SUCCESS, payload: data.data }),
        error => { console.log(error); dispatch({ type: FETCHING_PINNED_DISCUSSIONS_FAILURE }); }
      );
    }
  };
};

/**
 * Update sorting method
 * @param  {String} method
 * @return {action}
 */
export const updateSortingMethod = (method) => {
  return { type: UPDATE_SORTING_METHOD, payload: method };
};


================================================
FILE: frontend/Views/ForumFeed/api.js
================================================
import axios from 'axios';

/**
 * feed apis
 */
export const fetchDiscussions = (forum_id, sortingMethod) => {
  return axios.get(`/api/forum/${forum_id}/discussions?sorting_method=${sortingMethod}`);
};

export const fetchPinnedDiscussions = (forum_id) => {
  return axios.get(`/api/forum/${forum_id}/pinned_discussions`);
};


================================================
FILE: frontend/Views/ForumFeed/constants.js
================================================
export const START_FETCHING_DISCUSSIONS = 'start_fetching_discussions';
export const STOP_FETCHING_DISCUSSIONS = 'stop_fetching_discussions';
export const FETCHING_DISCUSSIONS_SUCCESS = 'fetching_discussions_success';
export const FETCHING_DISCUSSIONS_FAILURE = 'fetching_discussions_failure';

export const START_FETCHING_PINNED_DISCUSSIONS = 'start_fetching_pinned_discussions';
export const STOP_FETCHING_PINNED_DISCUSSIONS = 'stop_fetching_pinned_discussions';
export const FETCHING_PINNED_DISCUSSIONS_SUCCESS = 'fetching_pinned_discussions_success';
export const FETCHING_PINNED_DISCUSSIONS_FAILURE = 'fetching_pinned_discussions_failure';

export const UPDATE_SORTING_METHOD = 'update_sorting_method';
export const INVALID_FORUM = 'invalid_forum';


================================================
FILE: frontend/Views/ForumFeed/index.js
================================================
import React, { Component } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import classnames from 'classnames';

import {
  getDiscussions,
  getPinnedDiscussions,
  updateSortingMethod,
} from './actions';

import Button from 'Components/Button';
import FeedBox from 'Components/FeedBox';
import SideBar from 'Components/SideBar';

import appLayout from 'SharedStyles/appLayout.css';
import styles from './styles.css';

class ForumFeed extends Component {
  componentDidMount() {
    const {
      currentForumId,
      getDiscussions,
      getPinnedDiscussions,
    } = this.props;

    // get the discussions and pinned discussions
    getDiscussions(currentForumId());
    getPinnedDiscussions(currentForumId());
  }

  componentDidUpdate(prevProps) {
    const {
      currentForum,
      currentForumId,
      getDiscussions,
      getPinnedDiscussions,
    } = this.props;

    // get the discussions again
    // if the forum didn't matched
    if (prevProps.currentForum !== currentForum) {
      const feedChanged = true;
      getDiscussions(currentForumId(), feedChanged);
      getPinnedDiscussions(currentForumId(), feedChanged);
    }
  }

  handleSortingChange(newSortingMethod) {
    const {
      currentForum,
      getDiscussions,
      updateSortingMethod,
      sortingMethod,
    } = this.props;

    if (sortingMethod !== newSortingMethod) {
      updateSortingMethod(newSortingMethod);
      getDiscussions(currentForum, false, true);
    }
  }

  renderNewDiscussionButtion() {
    const { currentForum } = this.props;

    return (
      <div className={classnames(appLayout.showOnMediumBP, styles.newDiscussionBtn)}>
        <Link to={`/${currentForum}/new_discussion`}>
          <Button type='outline' fullWidth noUppercase>
            New Discussion
          </Button>
        </Link>
      </div>
    );
  }

  render() {
    const {
      currentForum,
      discussions,
      fetchingDiscussions,
      pinnedDiscussions,
      fetchingPinnedDiscussions,
      sortingMethod,
      error,
    } = this.props;

    if (error) {
      return (
        <div className={classnames(styles.errorMsg)}>
          {error}
        </div>
      );
    }

    return (
      <div className={classnames(appLayout.constraintWidth, styles.contentArea)}>
        <Helmet><title>{`ReForum | ${currentForum}`}</title></Helmet>

        <div className={appLayout.primaryContent}>
          <FeedBox
            type='pinned'
            loading={fetchingPinnedDiscussions}
            discussions={pinnedDiscussions}
            currentForum={currentForum}
          />

          <FeedBox
            type='general'
            loading={fetchingDiscussions}
            discussions={discussions}
            currentForum={currentForum}
            onChangeSortingMethod={this.handleSortingChange.bind(this)}
            activeSortingMethod={sortingMethod}
          />

          { this.renderNewDiscussionButtion() }
        </div>

        <div className={appLayout.secondaryContent}>
          <SideBar currentForum={currentForum} />
        </div>
      </div>
    );
  }
}

export default connect(
  (state) => { return {
    currentForum: state.app.currentForum,
    currentForumId: () => {
      const currentForumObj = _.find(state.app.forums, { forum_slug: state.app.currentForum });
      if (currentForumObj) return currentForumObj._id;
      else return null;
    },
    fetchingDiscussions: state.feed.fetchingDiscussions,
    discussions: state.feed.discussions,
    fetchingPinnedDiscussions: state.feed.fetchingPinnedDiscussions,
    sortingMethod: state.feed.sortingMethod,
    pinnedDiscussions: state.feed.pinnedDiscussions,
    error: state.feed.error,
  }; },
  (dispatch) => { return {
    getDiscussions: (currentForumId, feedChanged, sortingMethod, sortingChanged) => { dispatch(getDiscussions(currentForumId, feedChanged, sortingMethod, sortingChanged)); },
    getPinnedDiscussions: (currentForumId, feedChanged) => { dispatch(getPinnedDiscussions(currentForumId, feedChanged)); },
    updateSortingMethod: (method) => { dispatch(updateSortingMethod(method)); },
  }; }
)(ForumFeed);


================================================
FILE: frontend/Views/ForumFeed/reducers.js
================================================
import {
  START_FETCHING_DISCUSSIONS,
  STOP_FETCHING_DISCUSSIONS,
  FETCHING_DISCUSSIONS_SUCCESS,
  FETCHING_DISCUSSIONS_FAILURE,

  START_FETCHING_PINNED_DISCUSSIONS,
  STOP_FETCHING_PINNED_DISCUSSIONS,
  FETCHING_PINNED_DISCUSSIONS_SUCCESS,
  FETCHING_PINNED_DISCUSSIONS_FAILURE,

  UPDATE_SORTING_METHOD,
  INVALID_FORUM,
} from './constants';

const initialState = {
  fetchingDiscussions: true,
  discussions: null,
  fetchingPinnedDiscussions: true,
  pinnedDiscussions: null,
  sortingMethod: 'date',
  error: null,
};

export const feedReducer = (state = initialState, action) => {
  switch(action.type) {
    case START_FETCHING_DISCUSSIONS:
      return Object.assign({}, state, {
        fetchingDiscussions: true,
        error: null,
      });;

    case STOP_FETCHING_DISCUSSIONS:
      return Object.assign({}, state, {
        fetchingDiscussions: false,
      });;

    case FETCHING_DISCUSSIONS_SUCCESS:
      return Object.assign({}, state, {
        discussions: action.payload,
        fetchingDiscussions: false,
        error: null,
      });

    case FETCHING_DISCUSSIONS_FAILURE:
      return Object.assign({}, state, {
        fetchingDiscussions: false,
        error: 'Unable to fetch discussions at the moment.',
      });

    case START_FETCHING_PINNED_DISCUSSIONS:
      return Object.assign({}, state, {
        fetchingPinnedDiscussions: true,
        error: null,
      });;

    case STOP_FETCHING_PINNED_DISCUSSIONS:
      return Object.assign({}, state, {
        fetchingPinnedDiscussions: false,
      });;

    case FETCHING_PINNED_DISCUSSIONS_SUCCESS:
      return Object.assign({}, state, {
        pinnedDiscussions: action.payload,
        fetchingPinnedDiscussions: false,
        error: null,
      });

    case FETCHING_PINNED_DISCUSSIONS_FAILURE:
      return Object.assign({}, state, {
        fetchingPinnedDiscussions: false,
        error: 'Unable to fetch pinned discussions at the moment.',
      });


    case UPDATE_SORTING_METHOD:
      return Object.assign({}, state, {
        sortingMethod: action.payload,
      });

    case INVALID_FORUM:
      return Object.assign({}, state, {
        error: 'Sorry, couldn\'t find the forum.',
        fetchingPinnedDiscussions: false,
        fetchingDiscussions: false,
      });

    default:
      return state;
  }
};


================================================
FILE: frontend/Views/ForumFeed/styles.css
================================================
@value largeBP from 'SharedStyles/globalStyles';

/**
 * Forum view styles
 */
.contentArea {
  display: flex;
  margin-top: 15px;
  @media largeBP { flex-flow: row nowrap; }
}

.newDiscussionBtn {
  margin-top: 20px;
}

.errorMsg {
  margin-top: 20px;
  color: rgba(231, 76, 60,1.0);
  text-align: center;
  font-size: 12px;
  font-weight: bold;
}


================================================
FILE: frontend/Views/ForumFeed/tests/actions.test.js
================================================
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import nock from 'nock';
import axios from 'axios';
import httpAdapter from 'axios/lib/adapters/http';
import * as actions from '../actions';
import * as constants from '../constants';

// configure axios
const host = 'http://localhost:8080';
axios.defaults.host = host;
axios.defaults.adapter = httpAdapter;

// configure the mock store
const middlewares = [thunk];
const mockStore = configureStore(middlewares);

/**
 * testing getPinnedDiscussions action
 * @type {String}
 */
describe('getPinnedDiscussions', () => {
  afterEach(() => {
    // clean nock instances after each test
    nock.cleanAll();
  });

  it('should call FETCHING_PINNED_DISCUSSIONS_SUCCESS when fetching pinned discussion has been done', () => {
    // define a mock forumId
    const forumId = '1122334455';

    // intercept axios calls with nock
    nock(host)
      .get(`/api/forum/${forumId}/pinned_discussions`)
      .reply(200, {
        pinnedDiscussions: [],
      });

    // expected action list
    const expectedActions = [
      { type: constants.START_FETCHING_PINNED_DISCUSSIONS },
      {
        type: constants.FETCHING_PINNED_DISCUSSIONS_SUCCESS,
        payload: { pinnedDiscussions: [] },
      },
    ];

    // initialize store with empty object
    const store = mockStore({});

    // perform the test
    return store.dispatch(actions.getPinnedDiscussions(forumId, true)).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
    });
  });
});

/**
 * test updateSortingMethod action
 * @type {String}
 */
describe('updateSortingMethod', () => {
  it('should update the sorting method', () => {
    const method = 'popularity';
    const expectedAction = {
      type: constants.UPDATE_SORTING_METHOD,
      payload: method,
    };

    expect(actions.updateSortingMethod(method)).toEqual(expectedAction);
  });
});


================================================
FILE: frontend/Views/NewDiscussion/actions.js
================================================
import { browserHistory } from 'react-router';
import {
  POSTING_DISCUSSION_START,
  POSTING_DISCUSSION_END,
  POSTING_DISCUSSION_SUCCESS,
  POSTING_DISCUSSION_FAILURE,

  UPDATE_DISCUSSION_TITLE,
  UPDATE_DISCUSSION_CONTENT,
  UPDATE_DISCUSSION_PIN_STATUS,
  UPDATE_DISCUSSION_TAGS,

  CLEAR_SUCCESS_MESSAGE,
} from './constants';
import { postDiscussionApi } from './api';

/**
 * post a new discussion
 * @param  {ObjectId} userId
 * @param  {ObjectId} forumId
 * @param  {String} currentForum
 * @return {action}
 */
export const postDiscussion = (userId, forumId, currentForum) => {
  return (dispatch, getState) => {
    dispatch({ type: POSTING_DISCUSSION_START });

    // validate discussion inputs
    // discussion values are in redux state
    const {
      title,
      content,
      tags,
      pinned,
    } = getState().newDiscussion;

    let validated = true;

    if (!userId || !forumId) {
      validated = false;
      return dispatch({
        type: POSTING_DISCUSSION_FAILURE,
        payload: 'Something is wrong with user/forum.',
      });
    }

    if (title === null || title.length < 15) {
      validated = false;
      return dispatch({
        type: POSTING_DISCUSSION_FAILURE,
        payload: 'Title should be at least 15 characters.',
      });
    }

    if (content === null || content.length === 0) {
      validated = false;
      return dispatch({
        type: POSTING_DISCUSSION_FAILURE,
        payload: 'Please write some content before posting.',
      });
    }

    if (tags === null || tags.length === 0) {
      validated = false;
      return dispatch({
        type: POSTING_DISCUSSION_FAILURE,
        payload: 'Please provide some tags.',
      });
    }

    // make api call if post is validated
    if (validated) {
      postDiscussionApi({
        userId,
        forumId,
        title,
        content,
        tags,
        pinned,
      }).then(
        (data) => {
          if (data.data.postCreated === true) {
            dispatch({ type: POSTING_DISCUSSION_SUCCESS });
            setTimeout(() => { dispatch({ type: CLEAR_SUCCESS_MESSAGE }); }, 2000);

            // issue a redirect to the newly reacted discussion
            browserHistory.push(`/${currentForum}/discussion/${data.data.discussion_slug}`);
          } else {
            dispatch({
              type: POSTING_DISCUSSION_FAILURE,
              payload: 'Something is wrong at our server end. Please try again later',
            });
          }
        },
        (error) => {
          dispatch({
            type: POSTING_DISCUSSION_FAILURE,
            payload: error,
          });
        }
      );
    }
  };
};

/**
 * update the discussion title in redux state (controlled input)
 * @param  {String} value
 * @return {action}
 */
export const updateDiscussionTitle = (value) => {
  return {
    type: UPDATE_DISCUSSION_TITLE,
    payload: value,
  };
};

/**
 * update discussion content in redux state (controlled input)
 * @param  {Object} value
 * @return {action}
 */
export const updateDiscussionContent = (value) => {
  return {
    type: UPDATE_DISCUSSION_CONTENT,
    payload: value,
  };
};

/**
 * update discussion pinned status in redux state (controlled input)
 * @param  {Boolean} value
 * @return {action}
 */
export const updateDiscussionPinStatus = (value) => {
  return {
    type: UPDATE_DISCUSSION_PIN_STATUS,
    payload: value,
  };
};

/**
 * update discussion tags in redux state (controlled input)
 * @param  {Array} value
 * @return {action}
 */
export const updateDiscussionTags = (value) => {
  return {
    type: UPDATE_DISCUSSION_TAGS,
    payload: value,
  };
};


================================================
FILE: frontend/Views/NewDiscussion/api.js
================================================
import axios from 'axios';

export const postDiscussionApi = (discussion) => {
  return axios.post('/api/discussion/newDiscussion', discussion);
};


================================================
FILE: frontend/Views/NewDiscussion/constants.js
================================================
export const POSTING_DISCUSSION_START = 'posting_discussion_start';
export const POSTING_DISCUSSION_END = 'posting_discussion_end';
export const POSTING_DISCUSSION_SUCCESS = 'posting_discussion_success';
export const POSTING_DISCUSSION_FAILURE = 'posting_discussion_failure';

export const UPDATE_DISCUSSION_TITLE = 'update_discussion_title';
export const UPDATE_DISCUSSION_CONTENT = 'update_discussion_content';
export const UPDATE_DISCUSSION_PIN_STATUS = 'update_discussion_pin_status';
export const UPDATE_DISCUSSION_TAGS = 'update_discussion_tags';

export const CLEAR_SUCCESS_MESSAGE = 'clear_success_message';


================================================
FILE: frontend/Views/NewDiscussion/index.js
================================================
import _ from 'lodash';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import classnames from 'classnames';

import RichEditor from 'Components/RichEditor';
import PinButton from 'Components/NewDiscussion/PinButton';
import TagsInput from 'Components/NewDiscussion/TagsInput';

import {
  postDiscussion,
  updateDiscussionTitle,
  updateDiscussionContent,
  updateDiscussionPinStatus,
  updateDiscussionTags,
} from './actions';

import styles from './styles.css';
import appLayout from 'SharedStyles/appLayout.css';

class NewDiscussion extends Component {
  constructor(props) {
    super(props);

    this.state = {
      forumId: null,
      userId: null,
      fatalError: null,
    };
  }

  componentDidMount() {
    const {
      user,
      currentForum,
      forums,
    } = this.props;

    this.setUserAndForumID(user, forums, currentForum);
  }

  componentWillReceiveProps(nextProps) {
    const {
      user,
      currentForum,
      forums,
    } = nextProps;

    this.setUserAndForumID(user, forums, currentForum);
  }

  setUserAndForumID(user, forums, currentForum) {
    const forumId = _.find(forums, { forum_slug: currentForum });
    if (forumId) {
      const currentForumId = forumId._id;
      this.setState({
        forumId: currentForumId,
        userId: user._id,
      });
    } else {
      this.setState({
        fatalError: 'Invalid forum buddy, go for the right one!',
      });
    }
  }

  renderEditor() {
    const {
      authenticated,
      role,
    } = this.props.user;

    const {
      updateDiscussionTitle,
      updateDiscussionContent,
      updateDiscussionPinStatus,
      updateDiscussionTags,
      postDiscussion,
      currentForum,
    } = this.props;

    const {
      title,
      content,
      tags,
      pinned,
    } = this.props.newDiscussion;

    const {
      forumId,
      userId,
    } = this.state;

    // only show the editor when user is authenticated
    if (authenticated) {
      return [
        <input
          key={'title'}
          type="text"
          className={styles.titleInput}
          placeholder={'Discussion title...'}
          value={title}
          onChange={(event) => { updateDiscussionTitle(event.target.value); }}
        />,
        (role === 'admin') && <PinButton
          key={'pinned'}
          value={pinned}
          onChange={(value) => { updateDiscussionPinStatus(value); }}
        />,
        <TagsInput
          key={'tags'}
          value={tags}
          onChange={(tags) => { updateDiscussionTags(tags); }}
        />,
        <RichEditor
          key={'content'}
          type='newDiscussion'
          value={content}
          onChange={(value) => { updateDiscussionContent(value); }}
          onSave={() => { postDiscussion(userId, forumId, currentForum); }}
        />,
      ];
    }

    return (
      <div className={classnames(appLayout.constraintWidth, styles.signInMsg)}>
        Please sign in before posting a new discussion.
      </div>
    );
  }

  render() {
    const { fatalError } = this.state;

    if (fatalError) { return (<div className={classnames(styles.errorMsg, styles.fatalError)}>{fatalError}</div>); }

    const { currentForum } = this.props;
    const {
      errorMsg,
      postingSuccess,
      postingDiscussion,
    } = this.props.newDiscussion;

    return (
      <div className={classnames(appLayout.constraintWidth, styles.content)}>
        <Helmet><title>ReForum | New Discussion</title></Helmet>

        <div className={styles.forumInfo}>
          You are creating a new discussion on <span className={styles.forumName}>{currentForum}</span> forum.
        </div>
        <div className={styles.errorMsg}>{errorMsg}</div>
        { postingSuccess && <div className={styles.successMsg}>Your discussion is created :-)</div> }
        { this.renderEditor() }
        { postingDiscussion && <div className={styles.postingMsg}>Posting discussion...</div> }
      </div>
    );
  }
}

export default connect(
  (state) => { return {
    user: state.user,
    forums: state.app.forums,
    currentForum: state.app.currentForum,
    newDiscussion: state.newDiscussion,
  }; },
  (dispatch) => { return {
    postDiscussion: (userId, forumId, currentForum) => { dispatch(postDiscussion(userId, forumId, currentForum)); },
    updateDiscussionTitle: (value) => { dispatch(updateDiscussionTitle(value)); },
    updateDiscussionContent: (value) => { dispatch(updateDiscussionContent(value)); },
    updateDiscussionPinStatus: (value) => { dispatch(updateDiscussionPinStatus(value)); },
    updateDiscussionTags: (value) => { dispatch(updateDiscussionTags(value)); },
  }; }
)(NewDiscussion);


================================================
FILE: frontend/Views/NewDiscussion/reducers.js
================================================
import {
  POSTING_DISCUSSION_START,
  POSTING_DISCUSSION_SUCCESS,
  POSTING_DISCUSSION_FAILURE,

  UPDATE_DISCUSSION_TITLE,
  UPDATE_DISCUSSION_CONTENT,
  UPDATE_DISCUSSION_PIN_STATUS,
  UPDATE_DISCUSSION_TAGS,

  CLEAR_SUCCESS_MESSAGE,
} from './constants';

const initialState = {
  postingSuccess: false,
  errorMsg: null,
  postingDiscussion: false,
  title: '',
  content: null,
  tags: [],
  pinned: false,
};

export const newDiscussionReducer = (state = initialState, action) => {
  switch (action.type) {
    case POSTING_DISCUSSION_START:
      return Object.assign({}, state, {
        postingDiscussion: true,
      });

    case POSTING_DISCUSSION_SUCCESS:
      return Object.assign({}, initialState, {
        postingSuccess: true,
        postingDiscussion: false,
        errorMsg: null,
      });

    case POSTING_DISCUSSION_FAILURE:
      return Object.assign({}, state, {
        postingSuccess: false,
        postingDiscussion: false,
        errorMsg: action.payload || 'Unable to post discussion.',
      });

    case CLEAR_SUCCESS_MESSAGE:
      return Object.assign({}, initialState, {
        postingSuccess: false,
      });

    case UPDATE_DISCUSSION_TITLE:
      return Object.assign({}, state, {
        title: action.payload,
      });

    case UPDATE_DISCUSSION_CONTENT:
      return Object.assign({}, state, {
        content: action.payload,
      });

    case UPDATE_DISCUSSION_PIN_STATUS:
      return Object.assign({}, state, {
        pinned: action.payload,
      });

    case UPDATE_DISCUSSION_TAGS:
      return Object.assign({}, state, {
        tags: action.payload,
      });

    default:
      return state;
  }
};


================================================
FILE: frontend/Views/NewDiscussion/styles.css
================================================
@value largeBP, borderColor, secondaryFontColor from 'SharedStyles/globalStyles';

.content {
  margin-top: 10px;
  position: relative;
}

.forumName {
  font-weight: bold;
}

.errorMsg {
  margin-top: 5px;
  color: rgba(231, 76, 60,1.0);
  font-size: 12px;
  font-weight: bold;
  line-height: 14px;
}

.fatalError {
  margin-top: 20px;
  text-align: center;
}

.titleInput {
  margin: 20px 0px 0px 0px;
  padding: 10px 10px;
  width: 100%;
  border: none;
  outline: none;
  background-color: none;
  font-size: 24px;
  font-weight: 300;
  /*border-bottom: 1px solid borderColor;*/
}

.signInMsg {
  padding: 80px 0px;
  color: secondaryFontColor;
  text-align: center;
}

.postingMsg {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0px;

  display: flex;
  justify-content: center;
  align-items: center;

  background-color: rgba(255, 255, 255, 0.8);
}


================================================
FILE: frontend/Views/NotFound/index.js
================================================
import React, { Component } from 'react';

class NotFound extends Component {
  render() {
    return (
      <h3>Coudn't found the url buddy. Please check it out.</h3>
    );
  }
}

export default NotFound;


================================================
FILE: frontend/Views/SingleDiscussion/actions.js
================================================
import {
  FETCHING_SINGLE_DISC_START,
  FETCHING_SINGLE_DISC_END,
  FETCHING_SINGLE_DISC_SUCCESS,
  FETCHING_SINGLE_DISC_FAILURE,

  TOGGLE_FAVORITE_START,
  TOGGLE_FAVORITE_SUCCESS,
  TOGGLE_FAVORITE_FAILURE,

  UPDATE_OPINION_CONTENT,

  POSTING_OPINION_START,
  POSTING_OPINION_SUCCESS,
  POSTING_OPINION_FAILURE,

  DELETE_DISC_START,
  DELETE_DISC_SUCCESS,
  DELETE_DISC_REDIRECT,
  DELETE_DISC_FAILURE,

  DELETE_OPINION_START,
  DELETE_OPINION_SUCCESS,
  DELETE_OPINION_FAILURE,
} from './constants';

import {
  fetchSingleDiscussion,
  fetchOpinions,
  toggleFavoriteApi,
  postOpinionApi,
  deletePostApi,
  deleteOpinionApi,
} from './api';

/**
 * get the discussion from server
 * @param  {String} discussionSlug
 * @return {action}
 */
export const getDiscussion = (discussionSlug) => {
  return (dispatch, getState) => {
    dispatch({ type: FETCHING_SINGLE_DISC_START });
    fetchSingleDiscussion(discussionSlug).then(
      data => {
        if (data.data) dispatch({ type: FETCHING_SINGLE_DISC_SUCCESS, payload: data.data });
        else dispatch({ type: FETCHING_SINGLE_DISC_FAILURE });
      },
      error => dispatch({ type: FETCHING_SINGLE_DISC_FAILURE })
    );
  };
};

/**
 * toggle favorite status of the discussion
 * @param  {ObjectId} discussionId
 * @return {action}
 */
export const toggleFavorite = (discussionId) => {
  return (dispatch, getState) => {
    dispatch({ type: TOGGLE_FAVORITE_START });

    toggleFavoriteApi(discussionId).then(
      data => {
        if (data.data._id) {
          dispatch({ type: TOGGLE_FAVORITE_SUCCESS });
          dispatch({ type: FETCHING_SINGLE_DISC_SUCCESS, payload: data.data });
        }
        else dispatch({ type: TOGGLE_FAVORITE_FAILURE });
      },
      error => dispatch({ type: TOGGLE_FAVORITE_FAILURE })
    );
  };
};

/**
 * update opinion content in redux state (controlled input)
 * @param  {Object} value
 * @return {action}
 */
export const updateOpinionContent = (value) => {
  return {
    type: UPDATE_OPINION_CONTENT,
    payload: value,
  };
};

/**
 * post an opinion
 * @param  {Object} opinion
 * @param  {String} discussionSlug
 * @return {action}
 */
export const postOpinion = (opinion, discussionSlug) => {
  return (dispatch, getState) => {
    // dispatch to show the posting message
    dispatch({ type: POSTING_OPINION_START });

    // validate the opinion
    if (!opinion.content || opinion.content.length < 20) {
      dispatch({ type: POSTING_OPINION_FAILURE, payload: 'Please provide a bit more info in your opinion....at least 20 characters.' });
    } else {
      // call the api to post the opinion
      postOpinionApi(opinion).then(
        data => {
          if (data.data._id) {
            // fetch the discussion to refresh the opinion list
            fetchSingleDiscussion(discussionSlug).then(
              data => {
                dispatch({ type: FETCHING_SINGLE_DISC_SUCCESS, payload: data.data });
                dispatch({ type: POSTING_OPINION_SUCCESS });
              },
              error => dispatch({ type: FETCHING_SINGLE_DISC_FAILURE })
            );
          }
          else dispatch({ type: POSTING_OPINION_FAILURE });
        },
        error => dispatch({ type: POSTING_OPINION_FAILURE })
      );
    }
  };
};

/**
 * delete the discussion post
 * @param  {String} discussionSlug
 * @return {action}
 */
export const deletePost = (discussionSlug) => {
  return (dispatch, getState) => {
    dispatch({ type: DELETE_DISC_START });
    deletePostApi(discussionSlug).then(
      data => {
        if (data.data.deleted) { dispatch({ type: DELETE_DISC_SUCCESS }); }
        else { dispatch({ type: DELETE_DISC_FAILURE }); }
      },
      error => dispatch({ type: DELETE_DISC_FAILURE })
    );
  };
};

/**
 * after a successfull deletion of a discussion
 * the user should be redirected to the home page
 * @return {action}
 */
export const deletedDiscussionRedirect = () => {
  return (dispatch, getState) => {
    dispatch({ type: DELETE_DISC_REDIRECT });
  };
};

/**
 * delete an opinion
 * @param  {ObjectId} opinionId
 * @param  {String} discussionSlug
 * @return {action}
 */
export const deleteOpinion = (opinionId, discussionSlug) => {
  return (dispatch, getState) => {
    // show the loading message
    dispatch({ type: DELETE_OPINION_START, payload: opinionId });

    // call the api
    deleteOpinionApi(opinionId).then(
      data => {
        if (data.data.deleted) {

          // fetch the discussion again to refresh the opinions
          fetchSingleDiscussion(discussionSlug).then(
            data => {
              dispatch({ type: DELETE_OPINION_SUCCESS });
              dispatch({ type: FETCHING_SINGLE_DISC_SUCCESS, payload: data.data });
            },
            error => dispatch({ type: FETCHING_SINGLE_DISC_FAILURE })
          );

        }
        else { dispatch({ type: DELETE_OPINION_FAILURE }); }
      },
      error => dispatch({ type: DELETE_OPINION_FAILURE })
    );
  };
};


================================================
FILE: frontend/Views/SingleDiscussion/api.js
================================================
import axios from 'axios';

/**
 * single discussion apis
 */
export const fetchSingleDiscussion = (discussion_slug) => {
  return axios.get(`/api/discussion/${discussion_slug}`);
};

export const toggleFavoriteApi = (discussion_id) => {
  return axios.put(`/api/discussion/toggleFavorite/${discussion_id}`);
};

export const postOpinionApi = (opinion) => {
  return axios.post('/api/opinion/newOpinion', opinion);
};

export const deletePostApi = (discussionSlug) => {
  return axios.delete(`/api/discussion/deleteDiscussion/${discussionSlug}`);
};

export const deleteOpinionApi = (opinionId) => {
  return axios.delete(`/api/opinion/deleteOpinion/${opinionId}`);
};


================================================
FILE: frontend/Views/SingleDiscussion/constants.js
================================================
export const FETCHING_SINGLE_DISC_START = 'fetching_single_discussion_start';
export const FETCHING_SINGLE_DISC_END = 'fetching_single_discussion_end';
export const FETCHING_SINGLE_DISC_SUCCESS = 'fetching_single_discussion_success';
export const FETCHING_SINGLE_DISC_FAILURE = 'fetching_single_discussion_failure';

export const TOGGLE_FAVORITE_START = 'toggle_favorite_start';
export const TOGGLE_FAVORITE_SUCCESS = 'toggle_favorite_success';
export const TOGGLE_FAVORITE_FAILURE = 'toggle_favorite_failure';

export const UPDATE_OPINION_CONTENT = 'update_opinion_content';

export const POSTING_OPINION_START = 'posting_opinion_start';
export const POSTING_OPINION_SUCCESS = 'posting_opinion_success';
export const POSTING_OPINION_FAILURE = 'posting_opinion_failure';

export const DELETE_DISC_START = 'delete_disc_start';
export const DELETE_DISC_SUCCESS = 'delete_disc_success';
export const DELETE_DISC_FAILURE = 'delete_disc_failure';
export const DELETE_DISC_REDIRECT = 'delete_disc_redirect';

export const DELETE_OPINION_START = 'delete_opinion_start';
export const DELETE_OPINION_SUCCESS = 'delete_opinion_success';
export const DELETE_OPINION_FAILURE = 'delete_opinion_failure';


================================================
FILE: frontend/Views/SingleDiscussion/index.js
================================================
import _ from 'lodash';
import React, { Component } from 'react';
import { browserHistory } from 'react-router';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import classnames from 'classnames';

import {
  getDiscussion,
  toggleFavorite,
  updateOpinionContent,
  postOpinion,
  deletePost,
  deletedDiscussionRedirect,
  deleteOpinion,
} from './actions';

import Discussion from 'Components/SingleDiscussion/Discussion';
import ReplyBox from 'Components/SingleDiscussion/ReplyBox';
import Opinion from 'Components/SingleDiscussion/Opinion';

import styles from './styles.css';
import appLayout from 'SharedStyles/appLayout.css';

class SingleDiscussion extends Component {
  constructor(props) {
    super(props);
    this.state = { opinionContent: '' };
  }

  componentDidMount() {
    const {
      forum,
      discussion,
    } = this.props.params;

    this.props.getDiscussion(discussion);
  }

  componentDidUpdate() {
    const {
      deletedDiscussion,
      deletedDiscussionRedirect,
    } = this.props;

    const { forum } = this.props.params;

    // check if the discussion is deleted and redirect the user
    if (deletedDiscussion) {
      browserHistory.push(`/${forum}`);
      setTimeout(() => { deletedDiscussionRedirect(); }, 100);
    }
  }

  componentWillUnmount() {
    // remove any existing opinion texts
    this.props.updateOpinionContent(null);
  }

  userFavoritedDiscussion(userId, favorites) {
    let favorited = false;
    for(let i = 0; i < favorites.length; i++) {
      if (favorites[i] === userId) favorited = true;
    }
    return favorited;
  }

  handleReplySubmit() {
    const {
      forums,
      postOpinion,
      discussion,
      opinionContent,
      userId,
    } = this.props;

    const discussion_slug = this.props.params.discussion;
    const forumSlug = this.props.params.forum;
    const forumId = _.find(forums, { forum_slug: forumSlug })._id;

    postOpinion(
      {
        forum_id: forumId,
        discussion_id: discussion._id,
        user_id: userId,
        content: opinionContent,
      },
      discussion_slug
    );
  }

  deleteDiscussion() {
    const { discussion } = this.props.params;
    const { deletePost } = this.props;
    deletePost(discussion);
  }

  deleteOpinion(opinionId) {
    const { discussion } = this.props.params;
    const { deleteOpinion } = this.props;
    deleteOpinion(opinionId, discussion);
  }

  render() {
    const {
      userAuthenticated,
      fetchingDiscussion,
      discussion,
      toggleFavorite,
      toggleingFavorite,
      updateOpinionContent,
      postingOpinion,
      opinionError,
      deletingOpinion,
      deletingDiscussion,
      error,
    } = this.props;

    if (error) {
      return (<div className={styles.errorMsg}>{error}</div>);
    }

    // return loading status if discussion is not fetched yet
    if (fetchingDiscussion) {
      return <div className={styles.loadingWrapper}>Loading discussion ...</div>;
    }

    const {
      _id,
      content,
      date,
      favorites,
      title,
      tags,
      opinions,
    } = discussion;

    const {
      avatarUrl,
      name,
      username,
    } = discussion.user;

    // check if logged in user is owner of the discussion
    let allowDelete = false;
    if (
      (discussion.user._id === this.props.userId) ||
      this.props.userRole === 'admin'
    ) allowDelete = true;

    // check if user favorated the discussion
    const userFavorited = this.userFavoritedDiscussion(this.props.userId, favorites);

    return (
      <div className={appLayout.constraintWidth}>
        <Helmet><title>{`${title} | ReForum`}</title></Helmet>

        <Discussion
          id={_id}
          userAvatar={avatarUrl}
          userName={name}
          userGitHandler={username}
          discTitle={title}
          discDate={date}
          discContent={content}
          tags={tags}
          favoriteCount={favorites.length}
          favoriteAction={toggleFavorite}
          userFavorited={userFavorited}
          toggleingFavorite={toggleingFavorite}
          allowDelete={allowDelete}
          deletingDiscussion={deletingDiscussion}
          deleteAction={this.deleteDiscussion.bind(this)}
        />

        { opinionError && <div className={styles.errorMsg}>{opinionError}</div> }

        { !userAuthenticated && <div className={styles.signInMsg}>Please sign in to post a reply.</div> }
        { userAuthenticated && <ReplyBox
          posting={postingOpinion}
          onSubmit={this.handleReplySubmit.bind(this)}
          onChange={(content) => { updateOpinionContent(content); }}
        /> }

        { opinions && opinions.map((opinion) => {
          return (
            <Opinion
              key={opinion._id}
              opinionId={opinion._id}
              userAvatar={opinion.user.avatarUrl}
              userName={opinion.user.name}
              userGitHandler={opinion.user.username}
              opDate={opinion.date}
              opContent={opinion.content}
              userId={opinion.user_id}
              currentUserId={this.props.userId}
              currentUserRole={this.props.userRole}
              deleteAction={this.deleteOpinion.bind(this)}
              deletingOpinion={deletingOpinion}
            />
          );
        }) }
      </div>
    );
  }
}

export default connect(
  (state) => { return {
    forums: state.app.forums,
    userAuthenticated: state.user.authenticated,
    userId: state.user._id,
    userRole: state.user.role,
    fetchingDiscussion: state.discussion.fetchingDiscussion,
    toggleingFavorite: state.discussion.toggleingFavorite,
    deletingDiscussion: state.discussion.deletingDiscussion,
    deletedDiscussion: state.discussion.deletedDiscussion,
    opinionContent: state.discussion.opinionContent,
    postingOpinion: state.discussion.postingOpinion,
    opinionError: state.discussion.opinionError,
    deletingOpinion: state.discussion.deletingOpinion,
    discussion: state.discussion.discussion,
    error: state.discussion.error,
  }; },
  (dispatch) => { return {
    getDiscussion: (discussionSlug) => { dispatch(getDiscussion(discussionSlug)); },
    toggleFavorite: (discussionId) => { dispatch(toggleFavorite(discussionId)); },
    updateOpinionContent: (content) => { dispatch(updateOpinionContent(content)); },
    postOpinion: (opinion, discussionSlug) => { dispatch(postOpinion(opinion, discussionSlug)); },
    deletePost: (discussionSlug) => { dispatch(deletePost(discussionSlug)); },
    deletedDiscussionRedirect: () => { dispatch(deletedDiscussionRedirect()); },
    deleteOpinion: (opinionId, discussionSlug) => { dispatch(deleteOpinion(opinionId, discussionSlug)); },
  }; }
)(SingleDiscussion);


================================================
FILE: frontend/Views/SingleDiscussion/reducers.js
================================================
import {
  FETCHING_SINGLE_DISC_START,
  FETCHING_SINGLE_DISC_END,
  FETCHING_SINGLE_DISC_SUCCESS,
  FETCHING_SINGLE_DISC_FAILURE,

  TOGGLE_FAVORITE_START,
  TOGGLE_FAVORITE_SUCCESS,
  TOGGLE_FAVORITE_FAILURE,

  UPDATE_OPINION_CONTENT,

  POSTING_OPINION_START,
  POSTING_OPINION_SUCCESS,
  POSTING_OPINION_FAILURE,

  DELETE_DISC_START,
  DELETE_DISC_SUCCESS,
  DELETE_DISC_FAILURE,
  DELETE_DISC_REDIRECT,

  DELETE_OPINION_START,
  DELETE_OPINION_SUCCESS,
  DELETE_OPINION_FAILURE,
} from './constants';

const initialState = {
  fetchingDiscussion: true,
  toggleingFavorite: false,
  postingOpinion: false,
  opinionContent: null,
  opinionError: null,
  deletingDiscussion: false,
  deletedDiscussion: false,
  deletingOpinion: null,
  discussion: null,
  error: null,
};

export const singleDiscussionReducer = (state = initialState, action) => {
  switch(action.type) {
    case FETCHING_SINGLE_DISC_START:
      return Object.assign({}, state, {
        fetchingDiscussion: true,
      });

    case FETCHING_SINGLE_DISC_END:
      return Object.assign({}, state, {
        fetchingDiscussion: false,
      });

    case FETCHING_SINGLE_DISC_SUCCESS:
      return Object.assign({}, state, {
        discussion: action.payload,
        fetchingDiscussion: false,
        error: null,
      });

    case FETCHING_SINGLE_DISC_FAILURE:
      return Object.assign({}, state, {
        fetchingDiscussion: false,
        error: 'Unable to fetch discussion. Please check out the url.',
      });

    case TOGGLE_FAVORITE_START:
      return Object.assign({}, state, {
        toggleingFavorite: true,
      });

    case TOGGLE_FAVORITE_SUCCESS:
    case TOGGLE_FAVORITE_FAILURE:
      return Object.assign({}, state, {
        toggleingFavorite: false,
      });

    case UPDATE_OPINION_CONTENT:
      return Object.assign({}, state, {
        opinionContent: action.payload,
      });

    case POSTING_OPINION_START:
      return Object.assign({}, state, {
        postingOpinion: true,
        opinionError: null,
      });

    case POSTING_OPINION_SUCCESS:
      return Object.assign({}, state, {
        postingOpinion: false,
        opinionContent: null,
        opinionError: null,
      });

    case POSTING_OPINION_FAILURE:
      return Object.assign({}, state, {
        postingOpinion: false,
        opinionContent: null,
        opinionError: action.payload,
      });

    case DELETE_DISC_START:
      return Object.assign({}, state, {
        deletingDiscussion: true,
      });

    case DELETE_DISC_SUCCESS:
      return Object.assign({}, state, {
        deletingDiscussion: false,
        deletedDiscussion: true,
      });

    case DELETE_DISC_FAILURE:
      return Object.assign({}, state, {
        deletingDiscussion: false,
        deletedDiscussion: false,
      });

    case DELETE_DISC_REDIRECT:
      return Object.assign({}, state, {
        deletedDiscussion: false,
      });

    case DELETE_OPINION_START:
      return Object.assign({}, state, {
        deletingOpinion: action.payload,
      });

    case DELETE_OPINION_SUCCESS:
    case DELETE_OPINION_FAILURE:
      return Object.assign({}, state, {
        deletingOpinion: null,
      });

    default:
      return state;
  }
};


================================================
FILE: frontend/Views/SingleDiscussion/styles.css
================================================
@value primaryFontColor, secondaryFontColor from 'SharedStyles/globalStyles.css';

.loadingWrapper {
  padding: 20px 0px;
  font-size: 12px;
  font-weight: bold;
  text-align: center;
  color: secondaryFontColor;
}

.errorMsg {
  margin-top: 20px;
  margin-left: 10px;
  font-size: 12px;
  font-weight: bold;
  color: rgba(231, 76, 60,1.0);
  text-align: center;
}

.signInMsg {
  color: secondaryFontColor;
  font-size: 12px;
  font-weight: bold;
  text-align: center;
  padding: 20px 0px;
}


================================================
FILE: frontend/Views/UserProfile/actions.js
================================================
import {
  FETCH_USER_PROFILE_START,
  FETCH_USER_PROFILE_SUCCESS,
  FETCH_USER_PROFILE_FAILURE,
} from './constants';

import {
  fetchUserProfileApi,
} from './api';

/**
 * fetch the users profile from the server
 * @param  {String} userSlug
 * @return {action}
 */
export const fetchUserProfile = (userSlug) => {
  return (dispatch, getState) => {
    dispatch({ type: FETCH_USER_PROFILE_START });

    fetchUserProfileApi(userSlug).then(
      data => {
        if (data.data.error) dispatch({ type: FETCH_USER_PROFILE_FAILURE });
        else dispatch({ type: FETCH_USER_PROFILE_SUCCESS, payload: data.data });
      },
      error => dispatch({ type: FETCH_USER_PROFILE_FAILURE })
    );
  };
};


================================================
FILE: frontend/Views/UserProfile/api.js
================================================
/**
 * user profile apis
 */

import axios from 'axios';

export const fetchUserProfileApi = (userSlug) => {
  return axios.get(`/api/user/profile/${userSlug}`);
};


================================================
FILE: frontend/Views/UserProfile/constants.js
================================================
export const FETCH_USER_PROFILE_START = 'fetch_user_profile_start';
export const FETCH_USER_PROFILE_SUCCESS = 'fetch_user_profile_success';
export const FETCH_USER_PROFILE_FAILURE = 'fetch_user_profile_failure';


================================================
FILE: frontend/Views/UserProfile/index.js
================================================
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Helmet } from 'react-helmet';
import classnames from 'classnames';
import appLayout from 'SharedStyles/appLayout.css';
import styles from './styles.css';

// components used in this view
import Profile from 'Components/UserProfile/Profile';
import FeedBox from 'Components/FeedBox';

// actions
import {
  fetchUserProfile,
} from './actions';

class UserProfile extends Component {
  componentDidMount() {
    const { fetchUserProfile } = this.props;
    const { username } = this.props.params;
    fetchUserProfile(username);
  }

  componentWillReceiveProps(newProps) {
    // fetch profile if different username
    const { username: oldUsername } = this.props.params;
    const { username: futureUsername } = newProps.params;

    // only update if different usernames
    if (oldUsername !== futureUsername) {
      const { fetchUserProfile } = this.props;
      fetchUserProfile(futureUsername);
    }
  }

  render() {
    const {
      fetchingProfile,
      profile,
      error,
    } = this.props;

    if (error) {
      return <div className={styles.errorMsg}>{ error }</div>;
    }

    const {
      name,
      username,
      avatarUrl,
      github,
      discussions,
    } = profile;

    if (fetchingProfile) {
      return (
        <div className={classnames(appLayout.constraintWidth, styles.loadingMsg)}>
          Loading users profile ...
        </div>
      );
    }

    return (
      <div className={classnames(appLayout.constraintWidth, styles.container)}>
        <Helmet><title>{`${name || username} | ReForum`}</title></Helmet>

        <div className={appLayout.primaryContent}>
          <Profile
            name={name}
            gitHandler={username}
            location={github.location}
            avatarUrl={avatarUrl}
          />

          <FeedBox
            userProfile
            type='general'
            discussions={discussions}
          />
        </div>
      </div>
    );
  }
}

export default connect(
  (state) => { return {
    fetchingProfile: state.userProfile.fetchingProfile,
    profile: state.userProfile.profile,
    error: state.userProfile.error,
  }; },
  (dispatch) => { return {
    fetchUserProfile: (userSlug) => { dispatch(fetchUserProfile(userSlug)); },
  }; }
)(UserProfile);


================================================
FILE: frontend/Views/UserProfile/reducers.js
================================================
import {
  FETCH_USER_PROFILE_START,
  FETCH_USER_PROFILE_SUCCESS,
  FETCH_USER_PROFILE_FAILURE,
} from './constants';

const initialState = {
  fetchingProfile: true,
  profile: {},
  error: null,
};

export const userProfileReducer = (state = initialState, action) => {
  switch (action.type) {
    case FETCH_USER_PROFILE_START:
      return Object.assign({}, state, {
        fetchingProfile: true,
        error: null,
      });

    case FETCH_USER_PROFILE_SUCCESS:
      return Object.assign({}, state, {
        fetchingProfile: false,
        profile: action.payload,
        error: null,
      });

    case FETCH_USER_PROFILE_FAILURE:
      return Object.assign({}, state, {
        fetchingProfile: false,
        error: 'Unable to fetch user profile. Please check out for correct username.',
      });

    default:
      return state;
  }
};


================================================
FILE: frontend/Views/UserProfile/styles.css
================================================
@value largeBP from 'SharedStyles/globalStyles';

.container {
  margin-top: 20px;
  display: flex;
  @media largeBP { flex-flow: row nowrap; }
}

.loadingMsg {
  margin-top: 20px;
  text-align: center;
}

.errorMsg {
  margin-top: 20px;
  color: rgba(231, 76, 60,1.0);
  font-size: 12px;
  font-weight: bold;
  text-align: center;
}


================================================
FILE: package.json
================================================
{
  "name": "reforum",
  "version": "1.0.0",
  "description": "A forum application built with ReactJS, Redux, Express and MongoDB",
  "author": "Provash Shoumma",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/shoumma/ReForum/issues"
  },
  "homepage": "https://github.com/shoumma/ReForum#readme",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/shoumma/ReForum.git"
  },
  "keywords": [
    "forum",
    "react",
    "redux",
    "express",
    "mongodb"
  ],
  "main": "server.js",
  "engines": {
    "node": "7.1.0"
  },
  "scripts": {
    "test": "jest",
    "start": "better-npm-run start",
    "start:dev": "better-npm-run start:dev",
    "build": "webpack --config config/webpack.prod.config.js"
  },
  "betterScripts": {
    "start": {
      "command": "node server.js",
      "env": {
        "NODE_ENV": "production",
        "PORT": 3030
      }
    },
    "start:dev": {
      "command": "node server.js",
      "env": {
        "NODE_ENV": "development",
        "PORT": 8080
      }
    }
  },
  "dependencies": {
    "better-npm-run": "^0.0.13",
    "body-parser": "^1.15.2",
    "compression": "^1.6.2",
    "connect-flash": "^0.1.1",
    "connect-mongo": "^1.3.2",
    "cookie-parser": "^1.4.3",
    "express": "^4.14.0",
    "express-session": "^1.14.2",
    "help": "^3.0.2",
    "lodash": "^4.17.4",
    "mongoose": "^4.7.4",
    "morgan": "^1.7.0",
    "passport": "^0.3.2",
    "passport-github": "^1.1.0",
    "passport-local": "^1.0.0"
  },
  "devDependencies": {
    "async": "^2.1.5",
    "autoprefixer": "^6.6.1",
    "axios": "^0.15.3",
    "babel": "^6.5.2",
    "babel-core": "^6.20.0",
    "babel-eslint": "^7.1.1",
    "babel-jest": "^20.0.3",
    "babel-loader": "^6.2.9",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-react": "^6.16.0",
    "babel-preset-stage-2": "^6.18.0",
    "classnames": "^2.2.5",
    "css-loader": "^0.26.1",
    "draft-js": "^0.10.0",
    "eslint": "^3.12.1",
    "eslint-plugin-react": "^6.8.0",
    "extract-text-webpack-plugin": "^1.0.1",
    "file-loader": "^0.9.0",
    "jest": "^20.0.4",
    "moment": "^2.17.1",
    "nock": "^9.0.13",
    "postcss-loader": "^1.2.2",
    "postcss-nesting": "^2.3.1",
    "react": "^15.4.1",
    "react-dom": "^15.4.1",
    "react-helmet": "^5.0.3",
    "react-hot-loader": "^1.3.1",
    "react-onclickoutside": "^5.10.0",
    "react-redux": "^5.0.2",
    "react-router": "^3.0.0",
    "redux": "^3.6.0",
    "redux-mock-store": "^1.2.3",
    "redux-thunk": "^2.2.0",
    "style-loader": "^0.13.1",
    "url-loader": "^0.5.7",
    "webpack": "^1.14.0",
    "webpack-dev-middleware": "^1.8.4",
    "webpack-hot-middleware": "^2.13.2"
  }
}


================================================
FILE: public/build/bundle.js
================================================
!function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports,o,o.exports,e),o.loaded=!0,o.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){t.exports=n(257)},function(t,e){t.exports=React},function(t,e){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(t){if(l===setTimeout)return setTimeout(t,0);if((l===n||!l)&&setTimeout)return l=setTimeout,setTimeout(t,0);try{return l(t,0)}catch(e){try{return l.call(null,t,0)}catch(e){return l.call(this,t,0)}}}function i(t){if(f===clearTimeout)return clearTimeout(t);if((f===r||!f)&&clearTimeout)return f=clearTimeout,clearTimeout(t);try{return f(t)}catch(e){try{return f.call(null,t)}catch(e){return f.call(this,t)}}}function u(){y&&d&&(y=!1,d.length?h=d.concat(h):_=-1,h.length&&a())}function a(){if(!y){var t=o(u);y=!0;for(var e=h.length;e;){for(d=h,h=[];++_<e;)d&&d[_].run();_=-1,e=h.length}d=null,y=!1,i(t)}}function s(t,e){this.fun=t,this.array=e}function c(){}var l,f,p=t.exports={};!function(){try{l="function"==typeof setTimeout?setTimeout:n}catch(t){l=n}try{f="function"==typeof clearTimeout?clearTimeout:r}catch(t){f=r}}();var d,h=[],y=!1,_=-1;p.nextTick=function(t){var e=new Array(arguments.length-1);if(arguments.length>1)for(var n=1;n<arguments.length;n++)e[n-1]=arguments[n];h.push(new s(t,e)),1!==h.length||y||o(a)},s.prototype.run=function(){this.fun.apply(null,this.array)},p.title="browser",p.browser=!0,p.env={},p.argv=[],p.version="",p.versions={},p.on=c,p.addListener=c,p.once=c,p.off=c,p.removeListener=c,p.removeAllListeners=c,p.emit=c,p.prependListener=c,p.prependOnceListener=c,p.listeners=function(t){return[]},p.binding=function(t){throw new Error("process.binding is not supported")},p.cwd=function(){return"/"},p.chdir=function(t){throw new Error("process.chdir is not supported")},p.umask=function(){return 0}},function(t,e,n){(function(e){"use strict";function n(t,e,n,o,i,u,a,s){if(r(e),!t){var c;if(void 0===e)c=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var l=[n,o,i,u,a,s],f=0;c=new Error(e.replace(/%s/g,function(){return l[f++]})),c.name="Invariant Violation"}throw c.framesToPop=1,c}}var r=function(t){};"production"!==e.env.NODE_ENV&&(r=function(t){if(void 0===t)throw new Error("invariant requires an error message argument")}),t.exports=n}).call(e,n(2))},function(t,e,n){"use strict";function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e,n){return O.set(t,{selection:e,forceSelection:n,nativelyRenderedContent:null,inlineStyleOverride:null})}function i(t,e){return t.getBlockMap().map(function(n){return h.generate(t,n,e)}).toOrderedMap()}function u(t,e,n,r){var o=t.getCurrentContent().set("entityMap",n),i=o.getBlockMap(),u=t.getI
Download .txt
gitextract_bfqsak_0/

├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── LICENSE
├── README.md
├── backend/
│   ├── dev.js
│   ├── entities/
│   │   ├── admin/
│   │   │   ├── api.js
│   │   │   └── controller.js
│   │   ├── discussion/
│   │   │   ├── api.js
│   │   │   ├── controller.js
│   │   │   └── model.js
│   │   ├── forum/
│   │   │   ├── api.js
│   │   │   ├── controller.js
│   │   │   └── model.js
│   │   ├── opinion/
│   │   │   ├── api.js
│   │   │   ├── controller.js
│   │   │   └── model.js
│   │   └── user/
│   │       ├── api.js
│   │       ├── controller.js
│   │       └── model.js
│   ├── express.js
│   ├── mockData/
│   │   ├── discussions.js
│   │   ├── forum.js
│   │   ├── opinions.js
│   │   └── users.js
│   ├── passport.js
│   ├── routes.js
│   └── utilities/
│       └── tools.js
├── config/
│   ├── credentials.js
│   ├── serverConfig.js
│   ├── webpack.dev.config.js
│   └── webpack.prod.config.js
├── docs/
│   ├── api.md
│   └── system_overview.md
├── frontend/
│   ├── App/
│   │   ├── Admin.js
│   │   ├── App.js
│   │   ├── actions.js
│   │   ├── api.js
│   │   ├── constants.js
│   │   ├── index.js
│   │   ├── reducers.js
│   │   ├── store.js
│   │   └── styles.css
│   ├── Components/
│   │   ├── Button/
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   ├── Dashboard/
│   │   │   ├── Counts/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   └── ForumBox/
│   │   │       ├── index.js
│   │   │       └── styles.css
│   │   ├── FeedBox/
│   │   │   ├── DiscussionBox/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   ├── Footer/
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   ├── Header/
│   │   │   ├── Logo/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   ├── NavigationBar/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   └── UserMenu/
│   │   │       ├── index.js
│   │   │       └── styles.css
│   │   ├── NewDiscussion/
│   │   │   ├── PinButton/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   └── TagsInput/
│   │   │       ├── index.js
│   │   │       └── styles.css
│   │   ├── RichEditor/
│   │   │   ├── BlockStyleControls.js
│   │   │   ├── InlineStyleControls.js
│   │   │   ├── StyleButton.js
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   ├── SideBar/
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   ├── SingleDiscussion/
│   │   │   ├── Discussion/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   ├── Opinion/
│   │   │   │   ├── index.js
│   │   │   │   └── styles.css
│   │   │   └── ReplyBox/
│   │   │       ├── index.js
│   │   │       └── styles.css
│   │   ├── Tag/
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   └── UserProfile/
│   │       └── Profile/
│   │           ├── index.js
│   │           └── styles.css
│   ├── Containers/
│   │   ├── AdminHeader/
│   │   │   ├── index.js
│   │   │   └── styles.css
│   │   └── Header/
│   │       ├── index.js
│   │       └── styles.css
│   ├── SharedStyles/
│   │   ├── appLayout.css
│   │   └── globalStyles.css
│   └── Views/
│       ├── AdminDashboard/
│       │   ├── actions.js
│       │   ├── api.js
│       │   ├── constants.js
│       │   ├── index.js
│       │   ├── reducers.js
│       │   └── styles.css
│       ├── ForumFeed/
│       │   ├── actions.js
│       │   ├── api.js
│       │   ├── constants.js
│       │   ├── index.js
│       │   ├── reducers.js
│       │   ├── styles.css
│       │   └── tests/
│       │       └── actions.test.js
│       ├── NewDiscussion/
│       │   ├── actions.js
│       │   ├── api.js
│       │   ├── constants.js
│       │   ├── index.js
│       │   ├── reducers.js
│       │   └── styles.css
│       ├── NotFound/
│       │   └── index.js
│       ├── SingleDiscussion/
│       │   ├── actions.js
│       │   ├── api.js
│       │   ├── constants.js
│       │   ├── index.js
│       │   ├── reducers.js
│       │   └── styles.css
│       └── UserProfile/
│           ├── actions.js
│           ├── api.js
│           ├── constants.js
│           ├── index.js
│           ├── reducers.js
│           └── styles.css
├── package.json
├── public/
│   ├── build/
│   │   ├── bundle.js
│   │   └── style.css
│   └── index.html
└── server.js
Download .txt
SYMBOL INDEX (1433 symbols across 38 files)

FILE: backend/passport.js
  constant GITHUB_CLIENT_ID (line 7) | const GITHUB_CLIENT_ID = require('../config/credentials').GITHUB_CLIENT_ID;
  constant GITHUB_CLIENT_SECRET (line 8) | const GITHUB_CLIENT_SECRET = require('../config/credentials').GITHUB_CLI...
  constant GITHUB_CALLBACK_URL (line 9) | const GITHUB_CALLBACK_URL = require('../config/credentials').GITHUB_CALL...

FILE: frontend/App/Admin.js
  class AdminContainer (line 12) | class AdminContainer extends Component {
    method componentDidMount (line 13) | componentDidMount() {
    method render (line 18) | render() {

FILE: frontend/App/App.js
  class AppContainer (line 12) | class AppContainer extends Component {
    method componentDidMount (line 13) | componentDidMount() {
    method componentDidUpdate (line 32) | componentDidUpdate() {
    method render (line 48) | render() {

FILE: frontend/App/constants.js
  constant UPDATECURRENTFORUM (line 1) | const UPDATECURRENTFORUM = 'update_current_forum';
  constant START_FETCHING_FORUMS (line 3) | const START_FETCHING_FORUMS = 'start_fetching_forums';
  constant STOP_FETCHING_FORUMS (line 4) | const STOP_FETCHING_FORUMS = 'stop_fetching_forums';
  constant FETCHING_FORUMS_SUCCESS (line 5) | const FETCHING_FORUMS_SUCCESS = 'fetching_forums_success';
  constant FETCHING_FORUMS_FAILURE (line 6) | const FETCHING_FORUMS_FAILURE = 'fetching_forums_failure';
  constant START_FETCHING_USER (line 8) | const START_FETCHING_USER = 'start_fetching_user';
  constant FETCHING_USER_SUCCESS (line 9) | const FETCHING_USER_SUCCESS = 'fetching_user_success';
  constant FETCHING_USER_FAILURE (line 10) | const FETCHING_USER_FAILURE = 'fetching_user_failure';
  constant SIGNOUT_USER_SUCCESS (line 11) | const SIGNOUT_USER_SUCCESS = 'signOut_user_success';
  constant SIGNOUT_USER_FAILURE (line 12) | const SIGNOUT_USER_FAILURE = 'signOut_user_failure';

FILE: frontend/Components/Button/index.js
  class Button (line 5) | class Button extends Component {
    method render (line 6) | render() {

FILE: frontend/Components/Dashboard/Counts/index.js
  class Counts (line 5) | class Counts extends Component {
    method render (line 6) | render() {

FILE: frontend/Components/Dashboard/ForumBox/index.js
  class ForumBox (line 7) | class ForumBox extends Component {
    method constructor (line 8) | constructor(props) {
    method handleCreateForum (line 19) | handleCreateForum() {
    method render (line 63) | render() {

FILE: frontend/Components/FeedBox/DiscussionBox/index.js
  class DiscussionBox (line 9) | class DiscussionBox extends Component {
    method render (line 10) | render() {

FILE: frontend/Components/FeedBox/index.js
  class FeedBox (line 8) | class FeedBox extends Component {
    method renderSort (line 9) | renderSort() {
    method renderEmptyDiscussionLine (line 36) | renderEmptyDiscussionLine(loading, discussions) {
    method render (line 44) | render() {

FILE: frontend/Components/Footer/index.js
  class Footer (line 7) | class Footer extends Component {
    method render (line 8) | render() {

FILE: frontend/Components/Header/NavigationBar/index.js
  class NavigationBar (line 7) | class NavigationBar extends Component {
    method render (line 8) | render() {

FILE: frontend/Components/Header/UserMenu/index.js
  class UserMenu (line 9) | class UserMenu extends Component {
    method constructor (line 10) | constructor(props) {
    method handleClickOutside (line 16) | handleClickOutside() {
    method toggleSubMenu (line 20) | toggleSubMenu() {
    method renderSubMenu (line 26) | renderSubMenu() {
    method render (line 57) | render() {

FILE: frontend/Components/NewDiscussion/PinButton/index.js
  class PinButton (line 7) | class PinButton extends Component {
    method constructor (line 8) | constructor(props) {
    method componentWillReceiveProps (line 13) | componentWillReceiveProps(nextProps) {
    method updateValue (line 18) | updateValue(value) {
    method render (line 23) | render() {

FILE: frontend/Components/NewDiscussion/TagsInput/index.js
  class TagsInput (line 9) | class TagsInput extends Component {
    method constructor (line 10) | constructor(props) {
    method componentWillReceiveProps (line 20) | componentWillReceiveProps(nextProps) {
    method validateTag (line 25) | validateTag(tagName) {
    method sameTag (line 30) | sameTag(tagName) {
    method addTag (line 39) | addTag() {
    method removeTag (line 63) | removeTag(position) {
    method renderTags (line 70) | renderTags() {
    method renderInput (line 85) | renderInput() {
    method render (line 114) | render() {

FILE: frontend/Components/RichEditor/BlockStyleControls.js
  class BlockStyleControls (line 8) | class BlockStyleControls extends Component {
    method render (line 9) | render() {

FILE: frontend/Components/RichEditor/InlineStyleControls.js
  class InlineStyleControls (line 8) | class InlineStyleControls extends Component {
    method render (line 9) | render() {

FILE: frontend/Components/RichEditor/StyleButton.js
  class StyleButton (line 5) | class StyleButton extends React.Component {
    method constructor (line 6) | constructor() {
    method render (line 14) | render() {

FILE: frontend/Components/RichEditor/index.js
  class RichEditor (line 17) | class RichEditor extends Component {
    method constructor (line 18) | constructor(props) {
    method componentDidMount (line 32) | componentDidMount() {
    method onEditorStateChange (line 41) | onEditorStateChange(editorState) {
    method handleKeyCommand (line 49) | handleKeyCommand(command) {
    method onTab (line 58) | onTab(event) {
    method toggleBlockType (line 63) | toggleBlockType(blockType) {
    method toggleInlineStyle (line 72) | toggleInlineStyle(inlineStyle) {
    method customBlockStyles (line 81) | customBlockStyles(contentBlock) {
    method render (line 90) | render() {

FILE: frontend/Components/SideBar/index.js
  class SideBar (line 7) | class SideBar extends Component {
    method render (line 8) | render() {

FILE: frontend/Components/SingleDiscussion/Discussion/index.js
  class Discussion (line 13) | class Discussion extends Component {
    method render (line 14) | render() {

FILE: frontend/Components/SingleDiscussion/Opinion/index.js
  class Opinion (line 11) | class Opinion extends Component {
    method render (line 12) | render() {

FILE: frontend/Components/SingleDiscussion/ReplyBox/index.js
  class ReplyBox (line 6) | class ReplyBox extends Component {
    method render (line 7) | render() {

FILE: frontend/Components/Tag/index.js
  class Tag (line 7) | class Tag extends Component {
    method render (line 8) | render() {

FILE: frontend/Components/UserProfile/Profile/index.js
  class Profile (line 5) | class Profile extends Component {
    method render (line 6) | render() {

FILE: frontend/Containers/AdminHeader/index.js
  class AdminHeader (line 15) | class AdminHeader extends Component {
    method renderNavLinks (line 16) | renderNavLinks() {
    method render (line 22) | render() {

FILE: frontend/Containers/Header/index.js
  class Header (line 14) | class Header extends Component {
    method renderNavLinks (line 15) | renderNavLinks() {
    method render (line 31) | render() {

FILE: frontend/Views/AdminDashboard/constants.js
  constant GET_ALL_INFO_START (line 1) | const GET_ALL_INFO_START = 'get_all_info_start';
  constant GET_ALL_INFO_SUCCESS (line 2) | const GET_ALL_INFO_SUCCESS = 'get_all_info_success';
  constant GET_ALL_INFO_FAILURE (line 3) | const GET_ALL_INFO_FAILURE = 'get_all_info_failure';
  constant CREATE_FORUM (line 5) | const CREATE_FORUM = 'create_forum';
  constant CREATE_FORUM_SUCCESS (line 6) | const CREATE_FORUM_SUCCESS = 'create_forum_success';
  constant CREATE_FORUM_FAILURE (line 7) | const CREATE_FORUM_FAILURE = 'create_forum_failure';
  constant DELETE_FORUM (line 9) | const DELETE_FORUM = 'delete_forum';
  constant DELETE_FORUM_SUCCESS (line 10) | const DELETE_FORUM_SUCCESS = 'delete_forum_success';
  constant DELETE_FORUM_FAILURE (line 11) | const DELETE_FORUM_FAILURE = 'delete_forum_failure';

FILE: frontend/Views/AdminDashboard/index.js
  class Dashboard (line 17) | class Dashboard extends Component {
    method componentDidMount (line 18) | componentDidMount() {
    method render (line 23) | render() {

FILE: frontend/Views/ForumFeed/constants.js
  constant START_FETCHING_DISCUSSIONS (line 1) | const START_FETCHING_DISCUSSIONS = 'start_fetching_discussions';
  constant STOP_FETCHING_DISCUSSIONS (line 2) | const STOP_FETCHING_DISCUSSIONS = 'stop_fetching_discussions';
  constant FETCHING_DISCUSSIONS_SUCCESS (line 3) | const FETCHING_DISCUSSIONS_SUCCESS = 'fetching_discussions_success';
  constant FETCHING_DISCUSSIONS_FAILURE (line 4) | const FETCHING_DISCUSSIONS_FAILURE = 'fetching_discussions_failure';
  constant START_FETCHING_PINNED_DISCUSSIONS (line 6) | const START_FETCHING_PINNED_DISCUSSIONS = 'start_fetching_pinned_discuss...
  constant STOP_FETCHING_PINNED_DISCUSSIONS (line 7) | const STOP_FETCHING_PINNED_DISCUSSIONS = 'stop_fetching_pinned_discussio...
  constant FETCHING_PINNED_DISCUSSIONS_SUCCESS (line 8) | const FETCHING_PINNED_DISCUSSIONS_SUCCESS = 'fetching_pinned_discussions...
  constant FETCHING_PINNED_DISCUSSIONS_FAILURE (line 9) | const FETCHING_PINNED_DISCUSSIONS_FAILURE = 'fetching_pinned_discussions...
  constant UPDATE_SORTING_METHOD (line 11) | const UPDATE_SORTING_METHOD = 'update_sorting_method';
  constant INVALID_FORUM (line 12) | const INVALID_FORUM = 'invalid_forum';

FILE: frontend/Views/ForumFeed/index.js
  class ForumFeed (line 20) | class ForumFeed extends Component {
    method componentDidMount (line 21) | componentDidMount() {
    method componentDidUpdate (line 33) | componentDidUpdate(prevProps) {
    method handleSortingChange (line 50) | handleSortingChange(newSortingMethod) {
    method renderNewDiscussionButtion (line 64) | renderNewDiscussionButtion() {
    method render (line 78) | render() {

FILE: frontend/Views/NewDiscussion/constants.js
  constant POSTING_DISCUSSION_START (line 1) | const POSTING_DISCUSSION_START = 'posting_discussion_start';
  constant POSTING_DISCUSSION_END (line 2) | const POSTING_DISCUSSION_END = 'posting_discussion_end';
  constant POSTING_DISCUSSION_SUCCESS (line 3) | const POSTING_DISCUSSION_SUCCESS = 'posting_discussion_success';
  constant POSTING_DISCUSSION_FAILURE (line 4) | const POSTING_DISCUSSION_FAILURE = 'posting_discussion_failure';
  constant UPDATE_DISCUSSION_TITLE (line 6) | const UPDATE_DISCUSSION_TITLE = 'update_discussion_title';
  constant UPDATE_DISCUSSION_CONTENT (line 7) | const UPDATE_DISCUSSION_CONTENT = 'update_discussion_content';
  constant UPDATE_DISCUSSION_PIN_STATUS (line 8) | const UPDATE_DISCUSSION_PIN_STATUS = 'update_discussion_pin_status';
  constant UPDATE_DISCUSSION_TAGS (line 9) | const UPDATE_DISCUSSION_TAGS = 'update_discussion_tags';
  constant CLEAR_SUCCESS_MESSAGE (line 11) | const CLEAR_SUCCESS_MESSAGE = 'clear_success_message';

FILE: frontend/Views/NewDiscussion/index.js
  class NewDiscussion (line 22) | class NewDiscussion extends Component {
    method constructor (line 23) | constructor(props) {
    method componentDidMount (line 33) | componentDidMount() {
    method componentWillReceiveProps (line 43) | componentWillReceiveProps(nextProps) {
    method setUserAndForumID (line 53) | setUserAndForumID(user, forums, currentForum) {
    method renderEditor (line 68) | renderEditor() {
    method render (line 133) | render() {

FILE: frontend/Views/NotFound/index.js
  class NotFound (line 3) | class NotFound extends Component {
    method render (line 4) | render() {

FILE: frontend/Views/SingleDiscussion/constants.js
  constant FETCHING_SINGLE_DISC_START (line 1) | const FETCHING_SINGLE_DISC_START = 'fetching_single_discussion_start';
  constant FETCHING_SINGLE_DISC_END (line 2) | const FETCHING_SINGLE_DISC_END = 'fetching_single_discussion_end';
  constant FETCHING_SINGLE_DISC_SUCCESS (line 3) | const FETCHING_SINGLE_DISC_SUCCESS = 'fetching_single_discussion_success';
  constant FETCHING_SINGLE_DISC_FAILURE (line 4) | const FETCHING_SINGLE_DISC_FAILURE = 'fetching_single_discussion_failure';
  constant TOGGLE_FAVORITE_START (line 6) | const TOGGLE_FAVORITE_START = 'toggle_favorite_start';
  constant TOGGLE_FAVORITE_SUCCESS (line 7) | const TOGGLE_FAVORITE_SUCCESS = 'toggle_favorite_success';
  constant TOGGLE_FAVORITE_FAILURE (line 8) | const TOGGLE_FAVORITE_FAILURE = 'toggle_favorite_failure';
  constant UPDATE_OPINION_CONTENT (line 10) | const UPDATE_OPINION_CONTENT = 'update_opinion_content';
  constant POSTING_OPINION_START (line 12) | const POSTING_OPINION_START = 'posting_opinion_start';
  constant POSTING_OPINION_SUCCESS (line 13) | const POSTING_OPINION_SUCCESS = 'posting_opinion_success';
  constant POSTING_OPINION_FAILURE (line 14) | const POSTING_OPINION_FAILURE = 'posting_opinion_failure';
  constant DELETE_DISC_START (line 16) | const DELETE_DISC_START = 'delete_disc_start';
  constant DELETE_DISC_SUCCESS (line 17) | const DELETE_DISC_SUCCESS = 'delete_disc_success';
  constant DELETE_DISC_FAILURE (line 18) | const DELETE_DISC_FAILURE = 'delete_disc_failure';
  constant DELETE_DISC_REDIRECT (line 19) | const DELETE_DISC_REDIRECT = 'delete_disc_redirect';
  constant DELETE_OPINION_START (line 21) | const DELETE_OPINION_START = 'delete_opinion_start';
  constant DELETE_OPINION_SUCCESS (line 22) | const DELETE_OPINION_SUCCESS = 'delete_opinion_success';
  constant DELETE_OPINION_FAILURE (line 23) | const DELETE_OPINION_FAILURE = 'delete_opinion_failure';

FILE: frontend/Views/SingleDiscussion/index.js
  class SingleDiscussion (line 25) | class SingleDiscussion extends Component {
    method constructor (line 26) | constructor(props) {
    method componentDidMount (line 31) | componentDidMount() {
    method componentDidUpdate (line 40) | componentDidUpdate() {
    method componentWillUnmount (line 55) | componentWillUnmount() {
    method userFavoritedDiscussion (line 60) | userFavoritedDiscussion(userId, favorites) {
    method handleReplySubmit (line 68) | handleReplySubmit() {
    method deleteDiscussion (line 92) | deleteDiscussion() {
    method deleteOpinion (line 98) | deleteOpinion(opinionId) {
    method render (line 104) | render() {

FILE: frontend/Views/UserProfile/constants.js
  constant FETCH_USER_PROFILE_START (line 1) | const FETCH_USER_PROFILE_START = 'fetch_user_profile_start';
  constant FETCH_USER_PROFILE_SUCCESS (line 2) | const FETCH_USER_PROFILE_SUCCESS = 'fetch_user_profile_success';
  constant FETCH_USER_PROFILE_FAILURE (line 3) | const FETCH_USER_PROFILE_FAILURE = 'fetch_user_profile_failure';

FILE: frontend/Views/UserProfile/index.js
  class UserProfile (line 17) | class UserProfile extends Component {
    method componentDidMount (line 18) | componentDidMount() {
    method componentWillReceiveProps (line 24) | componentWillReceiveProps(newProps) {
    method render (line 36) | render() {

FILE: public/build/bundle.js
  function e (line 1) | function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,lo...
  function n (line 1) | function n(){throw new Error("setTimeout has not been defined")}
  function r (line 1) | function r(){throw new Error("clearTimeout has not been defined")}
  function o (line 1) | function o(t){if(l===setTimeout)return setTimeout(t,0);if((l===n||!l)&&s...
  function i (line 1) | function i(t){if(f===clearTimeout)return clearTimeout(t);if((f===r||!f)&...
  function u (line 1) | function u(){y&&d&&(y=!1,d.length?h=d.concat(h):_=-1,h.length&&a())}
  function a (line 1) | function a(){if(!y){var t=o(u);y=!0;for(var e=h.length;e;){for(d=h,h=[];...
  function s (line 1) | function s(t,e){this.fun=t,this.array=e}
  function c (line 1) | function c(){}
  function n (line 1) | function n(t,e,n,o,i,u,a,s){if(r(e),!t){var c;if(void 0===e)c=new Error(...
  function r (line 1) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 1) | function o(t,e,n){return O.set(t,{selection:e,forceSelection:n,nativelyR...
  function i (line 1) | function i(t,e){return t.getBlockMap().map(function(n){return h.generate...
  function u (line 1) | function u(t,e,n,r){var o=t.getCurrentContent().set("entityMap",n),i=o.g...
  function a (line 1) | function a(t,e,n,r,o){return n.merge(e.toSeq().filter(function(e){return...
  function s (line 1) | function s(t,e){var n=t.getLastChangeType();return e!==n||"insert-charac...
  function c (line 1) | function c(t,e){var n=e.getStartKey(),r=e.getStartOffset(),o=t.getBlockF...
  function l (line 1) | function l(t,e){var n=e.getStartKey(),r=e.getStartOffset(),o=t.getBlockF...
  function f (line 1) | function f(t,e){for(var n,r=t.getBlockBefore(e);r;){if(n=r.getLength())r...
  function t (line 1) | function t(e){r(this,t),this._immutable=e}
  function t (line 1) | function t(t,e){e&&(t.prototype=Object.create(e.prototype)),t.prototype....
  function e (line 1) | function e(t){return i(t)?t:A(t)}
  function n (line 1) | function n(t){return u(t)?t:D(t)}
  function r (line 1) | function r(t){return a(t)?t:x(t)}
  function o (line 1) | function o(t){return i(t)&&!s(t)?t:P(t)}
  function i (line 1) | function i(t){return!(!t||!t[cn])}
  function u (line 1) | function u(t){return!(!t||!t[ln])}
  function a (line 1) | function a(t){return!(!t||!t[fn])}
  function s (line 1) | function s(t){return u(t)||a(t)}
  function c (line 1) | function c(t){return!(!t||!t[pn])}
  function l (line 1) | function l(t){return t.value=!1,t}
  function f (line 1) | function f(t){t&&(t.value=!0)}
  function p (line 1) | function p(){}
  function d (line 1) | function d(t,e){e=e||0;for(var n=Math.max(0,t.length-e),r=new Array(n),o...
  function h (line 1) | function h(t){return void 0===t.size&&(t.size=t.__iterate(_)),t.size}
  function y (line 1) | function y(t,e){if("number"!=typeof e){var n=e>>>0;if(""+n!==e||42949672...
  function _ (line 1) | function _(){return!0}
  function g (line 1) | function g(t,e,n){return(0===t||void 0!==n&&t<=-n)&&(void 0===e||void 0!...
  function v (line 1) | function v(t,e){return b(t,e,0)}
  function m (line 1) | function m(t,e){return b(t,e,e)}
  function b (line 1) | function b(t,e,n){return void 0===t?n:t<0?Math.max(0,e+t):void 0===e?t:M...
  function E (line 1) | function E(t){this.next=t}
  function S (line 1) | function S(t,e,n,r){var o=0===t?e:1===t?n:[e,n];return r?r.value=o:r={va...
  function w (line 1) | function w(){return{value:void 0,done:!0}}
  function O (line 1) | function O(t){return!!I(t)}
  function T (line 1) | function T(t){return t&&"function"==typeof t.next}
  function C (line 1) | function C(t){var e=I(t);return e&&e.call(t)}
  function I (line 1) | function I(t){var e=t&&(wn&&t[wn]||t[On]);if("function"==typeof e)return e}
  function N (line 1) | function N(t){return t&&"number"==typeof t.length}
  function A (line 1) | function A(t){return null===t||void 0===t?L():i(t)?t.toSeq():K(t)}
  function D (line 1) | function D(t){return null===t||void 0===t?L().toKeyedSeq():i(t)?u(t)?t.t...
  function x (line 1) | function x(t){return null===t||void 0===t?L():i(t)?u(t)?t.entrySeq():t.t...
  function P (line 1) | function P(t){return(null===t||void 0===t?L():i(t)?u(t)?t.entrySeq():t:B...
  function k (line 1) | function k(t){this._array=t,this.size=t.length}
  function M (line 1) | function M(t){var e=Object.keys(t);this._object=t,this._keys=e,this.size...
  function R (line 1) | function R(t){this._iterable=t,this.size=t.length||t.size}
  function F (line 1) | function F(t){this._iterator=t,this._iteratorCache=[]}
  function j (line 1) | function j(t){return!(!t||!t[Cn])}
  function L (line 1) | function L(){return In||(In=new k([]))}
  function U (line 1) | function U(t){var e=Array.isArray(t)?new k(t).fromEntrySeq():T(t)?new F(...
  function B (line 1) | function B(t){var e=H(t);if(!e)throw new TypeError("Expected Array or it...
  function K (line 1) | function K(t){var e=H(t)||"object"==typeof t&&new M(t);if(!e)throw new T...
  function H (line 1) | function H(t){return N(t)?new k(t):T(t)?new F(t):O(t)?new R(t):void 0}
  function G (line 1) | function G(t,e,n,r){var o=t._cache;if(o){for(var i=o.length-1,u=0;u<=i;u...
  function z (line 1) | function z(t,e,n,r){var o=t._cache;if(o){var i=o.length-1,u=0;return new...
  function q (line 1) | function q(t,e){return e?W(e,t,"",{"":t}):V(t)}
  function W (line 1) | function W(t,e,n,r){return Array.isArray(e)?t.call(r,n,x(e).map(function...
  function V (line 1) | function V(t){return Array.isArray(t)?x(t).map(V).toList():Y(t)?D(t).map...
  function Y (line 1) | function Y(t){return t&&(t.constructor===Object||void 0===t.constructor)}
  function X (line 1) | function X(t,e){if(t===e||t!==t&&e!==e)return!0;if(!t||!e)return!1;if("f...
  function $ (line 1) | function $(t,e){if(t===e)return!0;if(!i(e)||void 0!==t.size&&void 0!==e....
  function J (line 1) | function J(t,e){if(!(this instanceof J))return new J(t,e);if(this._value...
  function Z (line 1) | function Z(t,e){if(!t)throw new Error(e)}
  function Q (line 1) | function Q(t,e,n){if(!(this instanceof Q))return new Q(t,e,n);if(Z(0!==n...
  function tt (line 1) | function tt(){throw TypeError("Abstract")}
  function et (line 1) | function et(){}
  function nt (line 1) | function nt(){}
  function rt (line 1) | function rt(){}
  function ot (line 1) | function ot(t){return t>>>1&1073741824|3221225471&t}
  function it (line 1) | function it(t){if(t===!1||null===t||void 0===t)return 0;if("function"==t...
  function ut (line 1) | function ut(t){var e=Bn[t];return void 0===e&&(e=at(t),Un===Ln&&(Un=0,Bn...
  function at (line 1) | function at(t){for(var e=0,n=0;n<t.length;n++)e=31*e+t.charCodeAt(n)|0;r...
  function st (line 1) | function st(t){var e;if(Mn&&(e=Dn.get(t),void 0!==e))return e;if(e=t[Fn]...
  function ct (line 1) | function ct(t){if(t&&t.nodeType>0)switch(t.nodeType){case 1:return t.uni...
  function lt (line 1) | function lt(t){Z(t!==1/0,"Cannot perform this action with an infinite si...
  function ft (line 1) | function ft(t){return null===t||void 0===t?St():pt(t)&&!c(t)?t:St().with...
  function pt (line 1) | function pt(t){return!(!t||!t[Kn])}
  function dt (line 1) | function dt(t,e){this.ownerID=t,this.entries=e}
  function ht (line 1) | function ht(t,e,n){this.ownerID=t,this.bitmap=e,this.nodes=n}
  function yt (line 1) | function yt(t,e,n){this.ownerID=t,this.count=e,this.nodes=n}
  function _t (line 1) | function _t(t,e,n){this.ownerID=t,this.keyHash=e,this.entries=n}
  function gt (line 1) | function gt(t,e,n){this.ownerID=t,this.keyHash=e,this.entry=n}
  function vt (line 1) | function vt(t,e,n){this._type=e,this._reverse=n,this._stack=t._root&&bt(...
  function mt (line 1) | function mt(t,e){return S(t,e[0],e[1])}
  function bt (line 1) | function bt(t,e){return{node:t,index:0,__prev:e}}
  function Et (line 1) | function Et(t,e,n,r){var o=Object.create(Hn);return o.size=t,o._root=e,o...
  function St (line 1) | function St(){return Gn||(Gn=Et(0))}
  function wt (line 1) | function wt(t,e,n){var r,o;if(t._root){var i=l(vn),u=l(mn);if(r=Ot(t._ro...
  function Ot (line 1) | function Ot(t,e,n,r,o,i,u,a){return t?t.update(e,n,r,o,i,u,a):i===gn?t:(...
  function Tt (line 1) | function Tt(t){return t.constructor===gt||t.constructor===_t}
  function Ct (line 1) | function Ct(t,e,n,r,o){if(t.keyHash===r)return new _t(e,r,[t.entry,o]);v...
  function It (line 1) | function It(t,e,n,r){t||(t=new p);for(var o=new gt(t,it(n),[n,r]),i=0;i<...
  function Nt (line 1) | function Nt(t,e,n,r){for(var o=0,i=0,u=new Array(n),a=0,s=1,c=e.length;a...
  function At (line 1) | function At(t,e,n,r,o){for(var i=0,u=new Array(yn),a=0;0!==n;a++,n>>>=1)...
  function Dt (line 1) | function Dt(t,e,r){for(var o=[],u=0;u<r.length;u++){var a=r[u],s=n(a);i(...
  function xt (line 1) | function xt(t,e,n){return t&&t.mergeDeep&&i(e)?t.mergeDeep(e):X(t,e)?t:e}
  function Pt (line 1) | function Pt(t){return function(e,n,r){if(e&&e.mergeDeepWith&&i(n))return...
  function kt (line 1) | function kt(t,e,n){return n=n.filter(function(t){return 0!==t.size}),0==...
  function Mt (line 1) | function Mt(t,e,n,r){var o=t===gn,i=e.next();if(i.done){var u=o?n:t,a=r(...
  function Rt (line 1) | function Rt(t){return t-=t>>1&1431655765,t=(858993459&t)+(t>>2&858993459...
  function Ft (line 1) | function Ft(t,e,n,r){var o=r?t:d(t);return o[e]=n,o}
  function jt (line 1) | function jt(t,e,n,r){var o=t.length+1;if(r&&e+1===o)return t[e]=n,t;for(...
  function Lt (line 1) | function Lt(t,e,n){var r=t.length-1;if(n&&e===r)return t.pop(),t;for(var...
  function Ut (line 1) | function Ut(t){var e=zt();if(null===t||void 0===t)return e;if(Bt(t))retu...
  function Bt (line 1) | function Bt(t){return!(!t||!t[Vn])}
  function Kt (line 1) | function Kt(t,e){this.array=t,this.ownerID=e}
  function Ht (line 1) | function Ht(t,e){function n(t,e,n){return 0===e?r(t,n):o(t,e,n)}function...
  function Gt (line 1) | function Gt(t,e,n,r,o,i,u){var a=Object.create(Yn);return a.size=e-t,a._...
  function zt (line 1) | function zt(){return Xn||(Xn=Gt(0,0,hn))}
  function qt (line 1) | function qt(t,e,n){if(e=y(t,e),e!==e)return t;if(e>=t.size||e<0)return t...
  function Wt (line 1) | function Wt(t,e,n,r,o,i){var u=r>>>n&_n,a=t&&u<t.array.length;if(!a&&voi...
  function Vt (line 1) | function Vt(t,e){return e&&t&&e===t.ownerID?t:new Kt(t?t.array.slice():[...
  function Yt (line 1) | function Yt(t,e){if(e>=Jt(t._capacity))return t._tail;if(e<1<<t._level+h...
  function Xt (line 1) | function Xt(t,e,n){void 0!==e&&(e|=0),void 0!==n&&(n|=0);var r=t.__owner...
  function $t (line 1) | function $t(t,e,n){for(var o=[],u=0,a=0;a<n.length;a++){var s=n[a],c=r(s...
  function Jt (line 1) | function Jt(t){return t<yn?0:t-1>>>hn<<hn}
  function Zt (line 1) | function Zt(t){return null===t||void 0===t?ee():Qt(t)?t:ee().withMutatio...
  function Qt (line 1) | function Qt(t){return pt(t)&&c(t)}
  function te (line 1) | function te(t,e,n,r){var o=Object.create(Zt.prototype);return o.size=t?t...
  function ee (line 1) | function ee(){return Jn||(Jn=te(St(),zt()))}
  function ne (line 1) | function ne(t,e,n){var r,o,i=t._map,u=t._list,a=i.get(e),s=void 0!==a;if...
  function re (line 1) | function re(t,e){this._iter=t,this._useKeys=e,this.size=t.size}
  function oe (line 1) | function oe(t){this._iter=t,this.size=t.size}
  function ie (line 1) | function ie(t){this._iter=t,this.size=t.size}
  function ue (line 1) | function ue(t){this._iter=t,this.size=t.size}
  function ae (line 1) | function ae(t){var e=Ne(t);return e._iter=t,e.size=t.size,e.flip=functio...
  function se (line 1) | function se(t,e,n){var r=Ne(t);return r.size=t.size,r.has=function(e){re...
  function ce (line 1) | function ce(t,e){var n=Ne(t);return n._iter=t,n.size=t.size,n.reverse=fu...
  function le (line 1) | function le(t,e,n,r){var o=Ne(t);return r&&(o.has=function(r){var o=t.ge...
  function fe (line 1) | function fe(t,e,n){var r=ft().asMutable();return t.__iterate(function(o,...
  function pe (line 1) | function pe(t,e,n){var r=u(t),o=(c(t)?Zt():ft()).asMutable();t.__iterate...
  function de (line 1) | function de(t,e,n,r){var o=t.size;if(void 0!==e&&(e|=0),void 0!==n&&(n|=...
  function he (line 1) | function he(t,e,n){var r=Ne(t);return r.__iterateUncached=function(r,o){...
  function ye (line 1) | function ye(t,e,n,r){var o=Ne(t);return o.__iterateUncached=function(o,i...
  function _e (line 1) | function _e(t,e){var r=u(t),o=[t].concat(e).map(function(t){return i(t)?...
  function ge (line 1) | function ge(t,e,n){var r=Ne(t);return r.__iterateUncached=function(r,o){...
  function ve (line 1) | function ve(t,e,n){var r=Ie(t);return t.toSeq().map(function(o,i){return...
  function me (line 1) | function me(t,e){var n=Ne(t);return n.size=t.size&&2*t.size-1,n.__iterat...
  function be (line 1) | function be(t,e,n){e||(e=De);var r=u(t),o=0,i=t.toSeq().map(function(e,r...
  function Ee (line 1) | function Ee(t,e,n){if(e||(e=De),n){var r=t.toSeq().map(function(e,r){ret...
  function Se (line 1) | function Se(t,e,n){var r=t(n,e);return 0===r&&n!==e&&(void 0===n||null==...
  function we (line 1) | function we(t,n,r){var o=Ne(t);return o.size=new k(r).map(function(t){re...
  function Oe (line 1) | function Oe(t,e){return j(t)?e:t.constructor(e)}
  function Te (line 1) | function Te(t){if(t!==Object(t))throw new TypeError("Expected [K, V] tup...
  function Ce (line 1) | function Ce(t){return lt(t.size),h(t)}
  function Ie (line 1) | function Ie(t){return u(t)?n:a(t)?r:o}
  function Ne (line 1) | function Ne(t){return Object.create((u(t)?D:a(t)?x:P).prototype)}
  function Ae (line 1) | function Ae(){return this._iter.cacheResult?(this._iter.cacheResult(),th...
  function De (line 1) | function De(t,e){return t>e?1:t<e?-1:0}
  function xe (line 1) | function xe(t){var n=C(t);if(!n){if(!N(t))throw new TypeError("Expected ...
  function Pe (line 1) | function Pe(t,e){var n,r=function(i){if(i instanceof r)return i;if(!(thi...
  function ke (line 1) | function ke(t,e,n){var r=Object.create(Object.getPrototypeOf(t));return ...
  function Me (line 1) | function Me(t){return t._name||t.constructor.name||"Record"}
  function Re (line 1) | function Re(t,e){try{e.forEach(Fe.bind(void 0,t))}catch(t){}}
  function Fe (line 1) | function Fe(t,e){Object.defineProperty(t,e,{get:function(){return this.g...
  function je (line 1) | function je(t){return null===t||void 0===t?Ke():Le(t)&&!c(t)?t:Ke().with...
  function Le (line 1) | function Le(t){return!(!t||!t[Qn])}
  function Ue (line 1) | function Ue(t,e){return t.__ownerID?(t.size=e.size,t._map=e,t):e===t._ma...
  function Be (line 1) | function Be(t,e){var n=Object.create(tr);return n.size=t?t.size:0,n._map...
  function Ke (line 1) | function Ke(){return er||(er=Be(St()));
  function He (line 2) | function He(t){return null===t||void 0===t?qe():Ge(t)?t:qe().withMutatio...
  function Ge (line 2) | function Ge(t){return Le(t)&&c(t)}
  function ze (line 2) | function ze(t,e){var n=Object.create(nr);return n.size=t?t.size:0,n._map...
  function qe (line 2) | function qe(){return rr||(rr=ze(ee()))}
  function We (line 2) | function We(t){return null===t||void 0===t?Xe():Ve(t)?t:Xe().unshiftAll(t)}
  function Ve (line 2) | function Ve(t){return!(!t||!t[or])}
  function Ye (line 2) | function Ye(t,e,n,r){var o=Object.create(ir);return o.size=t,o._head=e,o...
  function Xe (line 2) | function Xe(){return ur||(ur=Ye(0))}
  function $e (line 2) | function $e(t,e){var n=function(n){t.prototype[n]=e[n]};return Object.ke...
  function Je (line 2) | function Je(t,e){return e}
  function Ze (line 2) | function Ze(t,e){return[e,t]}
  function Qe (line 2) | function Qe(t){return function(){return!t.apply(this,arguments)}}
  function tn (line 2) | function tn(t){return function(){return-t.apply(this,arguments)}}
  function en (line 2) | function en(t){return"string"==typeof t?JSON.stringify(t):t}
  function nn (line 2) | function nn(){return d(arguments)}
  function rn (line 2) | function rn(t,e){return t<e?1:t>e?-1:0}
  function on (line 2) | function on(t){if(t.size===1/0)return 0;var e=c(t),n=u(t),r=e?1:0,o=t.__...
  function un (line 2) | function un(t,e){return e=xn(e,3432918353),e=xn(e<<15|e>>>-15,461845907)...
  function an (line 2) | function an(t,e){return t^e+2654435769+(t<<6)+(t>>2)|0}
  function n (line 8) | function n(){for(var t=[],e=0;e<arguments.length;e++){var r=arguments[e]...
  function r (line 8) | function r(t){return"[object Array]"===w.call(t)}
  function o (line 8) | function o(t){return"[object ArrayBuffer]"===w.call(t)}
  function i (line 8) | function i(t){return"undefined"!=typeof FormData&&t instanceof FormData}
  function u (line 8) | function u(t){var e;return e="undefined"!=typeof ArrayBuffer&&ArrayBuffe...
  function a (line 8) | function a(t){return"string"==typeof t}
  function s (line 8) | function s(t){return"number"==typeof t}
  function c (line 8) | function c(t){return"undefined"==typeof t}
  function l (line 8) | function l(t){return null!==t&&"object"==typeof t}
  function f (line 8) | function f(t){return"[object Date]"===w.call(t)}
  function p (line 8) | function p(t){return"[object File]"===w.call(t)}
  function d (line 8) | function d(t){return"[object Blob]"===w.call(t)}
  function h (line 8) | function h(t){return"[object Function]"===w.call(t)}
  function y (line 8) | function y(t){return l(t)&&h(t.pipe)}
  function _ (line 8) | function _(t){return"undefined"!=typeof URLSearchParams&&t instanceof UR...
  function g (line 8) | function g(t){return t.replace(/^\s*/,"").replace(/\s*$/,"")}
  function v (line 8) | function v(){return"undefined"!=typeof window&&"undefined"!=typeof docum...
  function m (line 8) | function m(t,e){if(null!==t&&"undefined"!=typeof t)if("object"==typeof t...
  function b (line 8) | function b(){function t(t,n){"object"==typeof e[n]&&"object"==typeof t?e...
  function E (line 8) | function E(t,e,n){return m(e,function(e,r){n&&"function"==typeof e?t[r]=...
  function t (line 8) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 8) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 8) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 8) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 8) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 8) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function r (line 8) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 8) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 8) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function e (line 8) | function e(){return r(this,e),o(this,t.apply(this,arguments))}
  function n (line 8) | function n(){for(var t=void 0;void 0===t||r.hasOwnProperty(t)||!isNaN(+t...
  function r (line 8) | function r(t,e,n,r){if(t===n)return!0;if(!n.startsWith(t))return!1;var o...
  function o (line 8) | function o(t){return"Windows"===i.platformName?t.replace(/^\s*NT/,""):t}
  function r (line 8) | function r(t){return t&&t.__esModule?t:{default:t}}
  function n (line 13) | function n(t){if(null===t||void 0===t)throw new TypeError("Object.assign...
  function r (line 13) | function r(){try{if(!Object.assign)return!1;var t=new String("abc");if(t...
  function r (line 13) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 13) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 13) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function u (line 13) | function u(t,e){return t.getStyle()===e.getStyle()}
  function a (line 13) | function a(t,e){return t.getEntity()===e.getEntity()}
  function e (line 13) | function e(){return r(this,e),o(this,t.apply(this,arguments))}
  function r (line 13) | function r(t){return p<=t&&t<=y}
  function o (line 13) | function o(t,n){if(0<=n&&n<t.length?void 0:"production"!==e.env.NODE_ENV...
  function i (line 13) | function i(t){return _.test(t)}
  function u (line 13) | function u(t,e){return 1+r(t.charCodeAt(e))}
  function a (line 13) | function a(t){if(!i(t))return t.length;for(var e=0,n=0;n<t.length;n+=u(t...
  function s (line 13) | function s(t,e,n){if(e=e||0,n=void 0===n?1/0:n||0,!i(t))return t.substr(...
  function c (line 13) | function c(t,e,n){e=e||0,n=void 0===n?1/0:n||0,e<0&&(e=0),n<0&&(n=0);var...
  function l (line 13) | function l(t){for(var e=[],n=0;n<t.length;n+=u(t,n))e.push(t.codePointAt...
  function n (line 13) | function n(t){return function(){return t}}
  function i (line 13) | function i(t,e){return t.set(e[0],e[1]),t}
  function u (line 13) | function u(t,e){return t.add(e),t}
  function a (line 13) | function a(t,e,n){switch(n.length){case 0:return t.call(e);case 1:return...
  function s (line 13) | function s(t,e,n,r){for(var o=-1,i=null==t?0:t.length;++o<i;){var u=t[o]...
  function c (line 13) | function c(t,e){for(var n=-1,r=null==t?0:t.length;++n<r&&e(t[n],n,t)!==!...
  function l (line 13) | function l(t,e){for(var n=null==t?0:t.length;n--&&e(t[n],n,t)!==!1;);ret...
  function f (line 13) | function f(t,e){for(var n=-1,r=null==t?0:t.length;++n<r;)if(!e(t[n],n,t)...
  function p (line 13) | function p(t,e){for(var n=-1,r=null==t?0:t.length,o=0,i=[];++n<r;){var u...
  function d (line 13) | function d(t,e){var n=null==t?0:t.length;return!!n&&O(t,e,0)>-1}
  function h (line 13) | function h(t,e,n){for(var r=-1,o=null==t?0:t.length;++r<o;)if(n(e,t[r]))...
  function y (line 13) | function y(t,e){for(var n=-1,r=null==t?0:t.length,o=Array(r);++n<r;)o[n]...
  function _ (line 13) | function _(t,e){for(var n=-1,r=e.length,o=t.length;++n<r;)t[o+n]=e[n];re...
  function g (line 13) | function g(t,e,n,r){var o=-1,i=null==t?0:t.length;for(r&&i&&(n=t[++o]);+...
  function v (line 13) | function v(t,e,n,r){var o=null==t?0:t.length;for(r&&o&&(n=t[--o]);o--;)n...
  function m (line 13) | function m(t,e){for(var n=-1,r=null==t?0:t.length;++n<r;)if(e(t[n],n,t))...
  function b (line 13) | function b(t){return t.split("")}
  function E (line 13) | function E(t){return t.match(Ge)||[]}
  function S (line 13) | function S(t,e,n){var r;return n(t,function(t,n,o){if(e(t,n,o))return r=...
  function w (line 13) | function w(t,e,n,r){for(var o=t.length,i=n+(r?1:-1);r?i--:++i<o;)if(e(t[...
  function O (line 13) | function O(t,e,n){return e===e?J(t,e,n):w(t,C,n)}
  function T (line 13) | function T(t,e,n,r){for(var o=n-1,i=t.length;++o<i;)if(r(t[o],e))return ...
  function C (line 13) | function C(t){return t!==t}
  function I (line 13) | function I(t,e){var n=null==t?0:t.length;return n?P(t,e)/n:jt}
  function N (line 13) | function N(t){return function(e){return null==e?ot:e[t]}}
  function A (line 13) | function A(t){return function(e){return null==t?ot:t[e]}}
  function D (line 13) | function D(t,e,n,r,o){return o(t,function(t,o,i){n=r?(r=!1,t):e(n,t,o,i)...
  function x (line 13) | function x(t,e){var n=t.length;for(t.sort(e);n--;)t[n]=t[n].value;return t}
  function P (line 13) | function P(t,e){for(var n,r=-1,o=t.length;++r<o;){var i=e(t[r]);i!==ot&&...
  function k (line 13) | function k(t,e){for(var n=-1,r=Array(t);++n<t;)r[n]=e(n);return r}
  function M (line 13) | function M(t,e){return y(e,function(e){return[e,t[e]]})}
  function R (line 13) | function R(t){return function(e){return t(e)}}
  function F (line 13) | function F(t,e){return y(e,function(e){return t[e]})}
  function j (line 13) | function j(t,e){return t.has(e)}
  function L (line 13) | function L(t,e){for(var n=-1,r=t.length;++n<r&&O(e,t[n],0)>-1;);return n}
  function U (line 13) | function U(t,e){for(var n=t.length;n--&&O(e,t[n],0)>-1;);return n}
  function B (line 13) | function B(t,e){for(var n=t.length,r=0;n--;)t[n]===e&&++r;return r}
  function K (line 13) | function K(t){return"\\"+nr[t]}
  function H (line 13) | function H(t,e){return null==t?ot:t[e]}
  function G (line 13) | function G(t){return Vn.test(t)}
  function z (line 13) | function z(t){return Yn.test(t)}
  function q (line 13) | function q(t){for(var e,n=[];!(e=t.next()).done;)n.push(e.value);return n}
  function W (line 13) | function W(t){var e=-1,n=Array(t.size);return t.forEach(function(t,r){n[...
  function V (line 13) | function V(t,e){return function(n){return t(e(n))}}
  function Y (line 13) | function Y(t,e){for(var n=-1,r=t.length,o=0,i=[];++n<r;){var u=t[n];u!==...
  function X (line 13) | function X(t){var e=-1,n=Array(t.size);return t.forEach(function(t){n[++...
  function $ (line 13) | function $(t){var e=-1,n=Array(t.size);return t.forEach(function(t){n[++...
  function J (line 13) | function J(t,e,n){for(var r=n-1,o=t.length;++r<o;)if(t[r]===e)return r;r...
  function Z (line 13) | function Z(t,e,n){for(var r=n+1;r--;)if(t[r]===e)return r;return r}
  function Q (line 13) | function Q(t){return G(t)?et(t):mr(t)}
  function tt (line 13) | function tt(t){return G(t)?nt(t):b(t)}
  function et (line 13) | function et(t){for(var e=qn.lastIndex=0;qn.test(t);)++e;return e}
  function nt (line 13) | function nt(t){return t.match(qn)||[]}
  function rt (line 13) | function rt(t){return t.match(Wn)||[]}
  function n (line 13) | function n(t){if(cs(t)&&!Ep(t)&&!(t instanceof b)){if(t instanceof o)ret...
  function r (line 13) | function r(){}
  function o (line 13) | function o(t,e){this.__wrapped__=t,this.__actions__=[],this.__chain__=!!...
  function b (line 13) | function b(t){this.__wrapped__=t,this.__actions__=[],this.__dir__=1,this...
  function A (line 13) | function A(){var t=new b(this.__wrapped__);return t.__actions__=Ko(this....
  function J (line 13) | function J(){if(this.__filtered__){var t=new b(this);t.__dir__=-1,t.__fi...
  function et (line 13) | function et(){var t=this.__wrapped__.value(),e=this.__dir__,n=Ep(t),r=e<...
  function nt (line 13) | function nt(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){va...
  function Ge (line 13) | function Ge(){this.__data__=af?af(null):{},this.size=0}
  function en (line 13) | function en(t){var e=this.has(t)&&delete this.__data__[t];return this.si...
  function nn (line 13) | function nn(t){var e=this.__data__;if(af){var n=e[t];return n===ct?ot:n}...
  function rn (line 13) | function rn(t){var e=this.__data__;return af?e[t]!==ot:bl.call(e,t)}
  function on (line 13) | function on(t,e){var n=this.__data__;return this.size+=this.has(t)?0:1,n...
  function un (line 13) | function un(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){va...
  function an (line 13) | function an(){this.__data__=[],this.size=0}
  function sn (line 13) | function sn(t){var e=this.__data__,n=kn(e,t);if(n<0)return!1;var r=e.len...
  function cn (line 13) | function cn(t){var e=this.__data__,n=kn(e,t);return n<0?ot:e[n][1]}
  function ln (line 13) | function ln(t){return kn(this.__data__,t)>-1}
  function fn (line 13) | function fn(t,e){var n=this.__data__,r=kn(n,t);return r<0?(++this.size,n...
  function pn (line 13) | function pn(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e<n;){va...
  function dn (line 13) | function dn(){this.size=0,this.__data__={hash:new nt,map:new(nf||un),str...
  function hn (line 13) | function hn(t){var e=Ci(this,t).delete(t);return this.size-=e?1:0,e}
  function yn (line 13) | function yn(t){return Ci(this,t).get(t)}
  function _n (line 13) | function _n(t){return Ci(this,t).has(t)}
  function gn (line 13) | function gn(t,e){var n=Ci(this,t),r=n.size;return n.set(t,e),this.size+=...
  function vn (line 13) | function vn(t){var e=-1,n=null==t?0:t.length;for(this.__data__=new pn;++...
  function mn (line 13) | function mn(t){return this.__data__.set(t,ct),this}
  function bn (line 13) | function bn(t){return this.__data__.has(t)}
  function En (line 13) | function En(t){var e=this.__data__=new un(t);this.size=e.size}
  function Sn (line 13) | function Sn(){this.__data__=new un,this.size=0}
  function wn (line 13) | function wn(t){var e=this.__data__,n=e.delete(t);return this.size=e.size,n}
  function On (line 13) | function On(t){return this.__data__.get(t)}
  function Tn (line 13) | function Tn(t){return this.__data__.has(t)}
  function Cn (line 13) | function Cn(t,e){var n=this.__data__;if(n instanceof un){var r=n.__data_...
  function In (line 13) | function In(t,e){var n=Ep(t),r=!n&&bp(t),o=!n&&!r&&wp(t),i=!n&&!r&&!o&&N...
  function Nn (line 13) | function Nn(t){var e=t.length;return e?t[no(0,e-1)]:ot}
  function An (line 13) | function An(t,e){return nu(Ko(t),Un(e,0,t.length))}
  function Dn (line 13) | function Dn(t){return nu(Ko(t))}
  function xn (line 13) | function xn(t,e,n){(n===ot||Xa(t[e],n))&&(n!==ot||e in t)||jn(t,e,n)}
  function Pn (line 13) | function Pn(t,e,n){var r=t[e];bl.call(t,e)&&Xa(r,n)&&(n!==ot||e in t)||j...
  function kn (line 13) | function kn(t,e){for(var n=t.length;n--;)if(Xa(t[n][0],e))return n;retur...
  function Mn (line 13) | function Mn(t,e,n,r){return mf(t,function(t,o,i){e(r,t,n(t),i)}),r}
  function Rn (line 13) | function Rn(t,e){return t&&Ho(e,zs(e),t)}
  function Fn (line 13) | function Fn(t,e){return t&&Ho(e,qs(e),t)}
  function jn (line 13) | function jn(t,e,n){"__proto__"==e&&Ll?Ll(t,e,{configurable:!0,enumerable...
  function Ln (line 13) | function Ln(t,e){for(var n=-1,r=e.length,o=ul(r),i=null==t;++n<r;)o[n]=i...
  function Un (line 13) | function Un(t,e,n){return t===t&&(n!==ot&&(t=t<=n?t:n),e!==ot&&(t=t>=e?t...
  function Bn (line 13) | function Bn(t,e,n,r,o,i){var u,a=e&pt,s=e&dt,l=e&ht;if(n&&(u=o?n(t,r,o,i...
  function Kn (line 13) | function Kn(t){var e=zs(t);return function(n){return Hn(n,t,e)}}
  function Hn (line 13) | function Hn(t,e,n){var r=n.length;if(null==t)return!r;for(t=fl(t);r--;){...
  function qn (line 13) | function qn(t,e,n){if("function"!=typeof t)throw new hl(st);return Mf(fu...
  function Wn (line 13) | function Wn(t,e,n,r){var o=-1,i=d,u=!0,a=t.length,s=[],c=e.length;if(!a)...
  function Vn (line 13) | function Vn(t,e){var n=!0;return mf(t,function(t,r,o){return n=!!e(t,r,o...
  function Yn (line 13) | function Yn(t,e,n){for(var r=-1,o=t.length;++r<o;){var i=t[r],u=e(i);if(...
  function Qn (line 13) | function Qn(t,e,n,r){var o=t.length;for(n=Cs(n),n<0&&(n=-n>o?0:o+n),r=r=...
  function tr (line 13) | function tr(t,e){var n=[];return mf(t,function(t,r,o){e(t,r,o)&&n.push(t...
  function er (line 13) | function er(t,e,n,r,o){var i=-1,u=t.length;for(n||(n=ji),o||(o=[]);++i<u...
  function nr (line 13) | function nr(t,e){return t&&Ef(t,e,zs)}
  function ir (line 13) | function ir(t,e){return t&&Sf(t,e,zs)}
  function ur (line 13) | function ur(t,e){return p(e,function(e){return is(t[e])})}
  function sr (line 13) | function sr(t,e){e=Io(e,t);for(var n=0,r=e.length;null!=t&&n<r;)t=t[ru(e...
  function cr (line 13) | function cr(t,e,n){var r=e(t);return Ep(t)?r:_(r,n(t))}
  function fr (line 13) | function fr(t){return null==t?t===ot?ae:Qt:jl&&jl in fl(t)?Ai(t):$i(t)}
  function pr (line 13) | function pr(t,e){return t>e}
  function mr (line 13) | function mr(t,e){return null!=t&&bl.call(t,e)}
  function wr (line 13) | function wr(t,e){return null!=t&&e in fl(t)}
  function Tr (line 13) | function Tr(t,e,n){return t>=$l(e,n)&&t<Xl(e,n)}
  function Cr (line 13) | function Cr(t,e,n){for(var r=n?h:d,o=t[0].length,i=t.length,u=i,a=ul(i),...
  function Ir (line 13) | function Ir(t,e,n,r){return nr(t,function(t,o,i){e(r,n(t),o,i)}),r}
  function Nr (line 13) | function Nr(t,e,n){e=Io(e,t),t=Zi(t,e);var r=null==t?t:t[ru(Tu(e))];retu...
  function Ar (line 13) | function Ar(t){return cs(t)&&fr(t)==Ht}
  function Dr (line 13) | function Dr(t){return cs(t)&&fr(t)==le}
  function xr (line 13) | function xr(t){return cs(t)&&fr(t)==Wt}
  function Pr (line 13) | function Pr(t,e,n,r,o){return t===e||(null==t||null==e||!cs(t)&&!cs(e)?t...
  function kr (line 13) | function kr(t,e,n,r,o,i){var u=Ep(t),a=Ep(e),s=u?Gt:xf(t),c=a?Gt:xf(e);s...
  function Mr (line 13) | function Mr(t){return cs(t)&&xf(t)==Jt}
  function Rr (line 13) | function Rr(t,e,n,r){var o=n.length,i=o,u=!r;if(null==t)return!i;for(t=f...
  function Fr (line 13) | function Fr(t){if(!ss(t)||Gi(t))return!1;var e=is(t)?Cl:Xe;return e.test...
  function jr (line 13) | function jr(t){return cs(t)&&fr(t)==re}
  function Lr (line 13) | function Lr(t){return cs(t)&&xf(t)==oe}
  function Ur (line 13) | function Ur(t){return cs(t)&&as(t.length)&&!!Jn[fr(t)]}
  function Br (line 13) | function Br(t){return"function"==typeof t?t:null==t?Mc:"object"==typeof ...
  function Kr (line 13) | function Kr(t){if(!zi(t))return Yl(t);var e=[];for(var n in fl(t))bl.cal...
  function Hr (line 13) | function Hr(t){if(!ss(t))return Xi(t);var e=zi(t),n=[];for(var r in t)("...
  function Gr (line 13) | function Gr(t,e){return t<e}
  function zr (line 13) | function zr(t,e){var n=-1,r=$a(t)?ul(t.length):[];return mf(t,function(t...
  function qr (line 13) | function qr(t){var e=Ii(t);return 1==e.length&&e[0][2]?Wi(e[0][0],e[0][1...
  function Wr (line 13) | function Wr(t,e){return Bi(t)&&qi(e)?Wi(ru(t),e):function(n){var r=Ks(n,...
  function Vr (line 13) | function Vr(t,e,n,r,o){t!==e&&Ef(e,function(i,u){if(ss(i))o||(o=new En),...
  function Yr (line 13) | function Yr(t,e,n,r,o,i,u){var a=t[n],s=e[n],c=u.get(s);if(c)return void...
  function Xr (line 13) | function Xr(t,e){var n=t.length;if(n)return e+=e<0?n:0,Li(e,n)?t[e]:ot}
  function $r (line 13) | function $r(t,e,n){var r=-1;e=y(e.length?e:[Mc],R(Ti()));var o=zr(t,func...
  function Jr (line 13) | function Jr(t,e){return Zr(t,e,function(e,n){return Gs(t,n)})}
  function Zr (line 13) | function Zr(t,e,n){for(var r=-1,o=e.length,i={};++r<o;){var u=e[r],a=sr(...
  function Qr (line 13) | function Qr(t){return function(e){return sr(e,t)}}
  function to (line 13) | function to(t,e,n,r){var o=r?T:O,i=-1,u=e.length,a=t;for(t===e&&(e=Ko(e)...
  function eo (line 13) | function eo(t,e){for(var n=t?e.length:0,r=n-1;n--;){var o=e[n];if(n==r||...
  function no (line 13) | function no(t,e){return t+Gl(Ql()*(e-t+1))}
  function ro (line 13) | function ro(t,e,n,r){for(var o=-1,i=Xl(Hl((e-t)/(n||1)),0),u=ul(i);i--;)...
  function oo (line 13) | function oo(t,e){var n="";if(!t||e<1||e>Rt)return n;do e%2&&(n+=t),e=Gl(...
  function io (line 13) | function io(t,e){return Rf(Ji(t,e,Mc),t+"")}
  function uo (line 13) | function uo(t){return Nn(rc(t))}
  function ao (line 13) | function ao(t,e){var n=rc(t);return nu(n,Un(e,0,n.length))}
  function so (line 13) | function so(t,e,n,r){if(!ss(t))return t;e=Io(e,t);for(var o=-1,i=e.lengt...
  function co (line 13) | function co(t){return nu(rc(t))}
  function lo (line 13) | function lo(t,e,n){var r=-1,o=t.length;e<0&&(e=-e>o?0:o+e),n=n>o?o:n,n<0...
  function fo (line 13) | function fo(t,e){var n;return mf(t,function(t,r,o){return n=e(t,r,o),!n}...
  function po (line 13) | function po(t,e,n){var r=0,o=null==t?r:t.length;if("number"==typeof e&&e...
  function ho (line 13) | function ho(t,e,n,r){e=n(e);for(var o=0,i=null==t?0:t.length,u=e!==e,a=n...
  function yo (line 13) | function yo(t,e){for(var n=-1,r=t.length,o=0,i=[];++n<r;){var u=t[n],a=e...
  function _o (line 13) | function _o(t){return"number"==typeof t?t:bs(t)?jt:+t}
  function go (line 13) | function go(t){if("string"==typeof t)return t;if(Ep(t))return y(t,go)+""...
  function vo (line 13) | function vo(t,e,n){var r=-1,o=d,i=t.length,u=!0,a=[],s=a;if(n)u=!1,o=h;e...
  function mo (line 13) | function mo(t,e){return e=Io(e,t),t=Zi(t,e),null==t||delete t[ru(Tu(e))]}
  function bo (line 13) | function bo(t,e,n,r){return so(t,e,n(sr(t,e)),r)}
  function Eo (line 13) | function Eo(t,e,n,r){for(var o=t.length,i=r?o:-1;(r?i--:++i<o)&&e(t[i],i...
  function So (line 13) | function So(t,e){var n=t;return n instanceof b&&(n=n.value()),g(e,functi...
  function wo (line 13) | function wo(t,e,n){var r=t.length;if(r<2)return r?vo(t[0]):[];for(var o=...
  function Oo (line 13) | function Oo(t,e,n){for(var r=-1,o=t.length,i=e.length,u={};++r<o;){var a...
  function To (line 13) | function To(t){return Ja(t)?t:[]}
  function Co (line 13) | function Co(t){return"function"==typeof t?t:Mc}
  function Io (line 13) | function Io(t,e){return Ep(t)?t:Bi(t,e)?[t]:Ff(xs(t))}
  function No (line 13) | function No(t,e,n){var r=t.length;return n=n===ot?r:n,!e&&n>=r?t:lo(t,e,n)}
  function Ao (line 13) | function Ao(t,e){if(e)return t.slice();var n=t.length,r=Dl?Dl(n):new t.c...
  function Do (line 13) | function Do(t){var e=new t.constructor(t.byteLength);return new Al(e).se...
  function xo (line 13) | function xo(t,e){var n=e?Do(t.buffer):t.buffer;return new t.constructor(...
  function Po (line 13) | function Po(t,e,n){var r=e?n(W(t),pt):W(t);return g(r,i,new t.constructor)}
  function ko (line 13) | function ko(t){var e=new t.constructor(t.source,We.exec(t));return e.las...
  function Mo (line 13) | function Mo(t,e,n){var r=e?n(X(t),pt):X(t);return g(r,u,new t.constructor)}
  function Ro (line 13) | function Ro(t){return _f?fl(_f.call(t)):{}}
  function Fo (line 13) | function Fo(t,e){var n=e?Do(t.buffer):t.buffer;return new t.constructor(...
  function jo (line 13) | function jo(t,e){if(t!==e){var n=t!==ot,r=null===t,o=t===t,i=bs(t),u=e!=...
  function Lo (line 13) | function Lo(t,e,n){for(var r=-1,o=t.criteria,i=e.criteria,u=o.length,a=n...
  function Uo (line 13) | function Uo(t,e,n,r){for(var o=-1,i=t.length,u=n.length,a=-1,s=e.length,...
  function Bo (line 13) | function Bo(t,e,n,r){for(var o=-1,i=t.length,u=-1,a=n.length,s=-1,c=e.le...
  function Ko (line 13) | function Ko(t,e){var n=-1,r=t.length;for(e||(e=ul(r));++n<r;)e[n]=t[n];r...
  function Ho (line 13) | function Ho(t,e,n,r){var o=!n;n||(n={});for(var i=-1,u=e.length;++i<u;){...
  function Go (line 13) | function Go(t,e){return Ho(t,Af(t),e)}
  function zo (line 13) | function zo(t,e){return Ho(t,Df(t),e)}
  function qo (line 13) | function qo(t,e){return function(n,r){var o=Ep(n)?s:Mn,i=e?e():{};return...
  function Wo (line 13) | function Wo(t){return io(function(e,n){var r=-1,o=n.length,i=o>1?n[o-1]:...
  function Vo (line 13) | function Vo(t,e){return function(n,r){if(null==n)return n;if(!$a(n))retu...
  function Yo (line 13) | function Yo(t){return function(e,n,r){for(var o=-1,i=fl(e),u=r(e),a=u.le...
  function Xo (line 13) | function Xo(t,e,n){function r(){var e=this&&this!==ar&&this instanceof r...
  function $o (line 13) | function $o(t){return function(e){e=xs(e);var n=G(e)?tt(e):ot,r=n?n[0]:e...
  function Jo (line 13) | function Jo(t){return function(e){return g(Ac(cc(e).replace(Gn,"")),t,"")}}
  function Zo (line 13) | function Zo(t){return function(){var e=arguments;switch(e.length){case 0...
  function Qo (line 13) | function Qo(t,e,n){function r(){for(var i=arguments.length,u=ul(i),s=i,c...
  function ti (line 13) | function ti(t){return function(e,n,r){var o=fl(e);if(!$a(e)){var i=Ti(n,...
  function ei (line 13) | function ei(t){return bi(function(e){var n=e.length,r=n,i=o.prototype.th...
  function ni (line 14) | function ni(t,e,n,r,o,i,u,a,s,c){function l(){for(var g=arguments.length...
  function ri (line 14) | function ri(t,e){return function(n,r){return Ir(n,t,e(r),{})}}
  function oi (line 14) | function oi(t,e){return function(n,r){var o;if(n===ot&&r===ot)return e;i...
  function ii (line 14) | function ii(t){return bi(function(e){return e=y(e,R(Ti())),io(function(n...
  function ui (line 14) | function ui(t,e){e=e===ot?" ":go(e);var n=e.length;if(n<2)return n?oo(e,...
  function ai (line 14) | function ai(t,e,n,r){function o(){for(var e=-1,s=arguments.length,c=-1,l...
  function si (line 14) | function si(t){return function(e,n,r){return r&&"number"!=typeof r&&Ui(e...
  function ci (line 14) | function ci(t){return function(e,n){return"string"==typeof e&&"string"==...
  function li (line 14) | function li(t,e,n,r,o,i,u,a,s,c){var l=e&bt,f=l?u:ot,p=l?ot:u,d=l?i:ot,h...
  function fi (line 14) | function fi(t){var e=ll[t];return function(t,n){if(t=Ns(t),n=null==n?0:$...
  function pi (line 14) | function pi(t){return function(e){var n=xf(e);return n==Jt?W(e):n==oe?$(...
  function di (line 14) | function di(t,e,n,r,o,i,u,a){var s=e&vt;if(!s&&"function"!=typeof t)thro...
  function hi (line 14) | function hi(t,e,n,r){return t===ot||Xa(t,gl[n])&&!bl.call(r,n)?e:t}
  function yi (line 14) | function yi(t,e,n,r,o,i){return ss(t)&&ss(e)&&(i.set(e,t),Vr(t,e,ot,yi,i...
  function _i (line 14) | function _i(t){return gs(t)?ot:t}
  function gi (line 14) | function gi(t,e,n,r,o,i){var u=n&yt,a=t.length,s=e.length;if(a!=s&&!(u&&...
  function vi (line 14) | function vi(t,e,n,r,o,i,u){switch(n){case fe:if(t.byteLength!=e.byteLeng...
  function mi (line 14) | function mi(t,e,n,r,o,i){var u=n&yt,a=Ei(t),s=a.length,c=Ei(e),l=c.lengt...
  function bi (line 14) | function bi(t){return Rf(Ji(t,ot,gu),t+"")}
  function Ei (line 14) | function Ei(t){return cr(t,zs,Af)}
  function Si (line 14) | function Si(t){return cr(t,qs,Df)}
  function wi (line 14) | function wi(t){for(var e=t.name+"",n=cf[e],r=bl.call(cf,e)?n.length:0;r-...
  function Oi (line 14) | function Oi(t){var e=bl.call(n,"placeholder")?n:t;return e.placeholder}
  function Ti (line 14) | function Ti(){var t=n.iteratee||Rc;return t=t===Rc?Br:t,arguments.length...
  function Ci (line 14) | function Ci(t,e){var n=t.__data__;return Ki(e)?n["string"==typeof e?"str...
  function Ii (line 14) | function Ii(t){for(var e=zs(t),n=e.length;n--;){var r=e[n],o=t[r];e[n]=[...
  function Ni (line 14) | function Ni(t,e){var n=H(t,e);return Fr(n)?n:ot}
  function Ai (line 14) | function Ai(t){var e=bl.call(t,jl),n=t[jl];try{t[jl]=ot;var r=!0}catch(t...
  function Di (line 14) | function Di(t,e,n){for(var r=-1,o=n.length;++r<o;){var i=n[r],u=i.size;s...
  function xi (line 14) | function xi(t){var e=t.match(Ke);return e?e[1].split(He):[]}
  function Pi (line 14) | function Pi(t,e,n){e=Io(e,t);for(var r=-1,o=e.length,i=!1;++r<o;){var u=...
  function ki (line 14) | function ki(t){var e=t.length,n=t.constructor(e);return e&&"string"==typ...
  function Mi (line 14) | function Mi(t){return"function"!=typeof t.constructor||zi(t)?{}:vf(xl(t))}
  function Ri (line 14) | function Ri(t,e,n,r){var o=t.constructor;switch(e){case le:return Do(t);...
  function Fi (line 14) | function Fi(t,e){var n=e.length;if(!n)return t;var r=n-1;return e[r]=(n>...
  function ji (line 14) | function ji(t){return Ep(t)||bp(t)||!!(Rl&&t&&t[Rl])}
  function Li (line 14) | function Li(t,e){return e=null==e?Rt:e,!!e&&("number"==typeof t||Je.test...
  function Ui (line 14) | function Ui(t,e,n){if(!ss(n))return!1;var r=typeof e;return!!("number"==...
  function Bi (line 14) | function Bi(t,e){if(Ep(t))return!1;var n=typeof t;return!("number"!=n&&"...
  function Ki (line 14) | function Ki(t){var e=typeof t;return"string"==e||"number"==e||"symbol"==...
  function Hi (line 14) | function Hi(t){var e=wi(t),r=n[e];if("function"!=typeof r||!(e in b.prot...
  function Gi (line 14) | function Gi(t){return!!Sl&&Sl in t}
  function zi (line 14) | function zi(t){var e=t&&t.constructor,n="function"==typeof e&&e.prototyp...
  function qi (line 14) | function qi(t){return t===t&&!ss(t)}
  function Wi (line 14) | function Wi(t,e){return function(n){return null!=n&&(n[t]===e&&(e!==ot||...
  function Vi (line 14) | function Vi(t){var e=Ra(t,function(t){return n.size===lt&&n.clear(),t}),...
  function Yi (line 14) | function Yi(t,e){var n=t[1],r=e[1],o=n|r,i=o<(gt|vt|Ot),u=r==Ot&&n==bt||...
  function Xi (line 14) | function Xi(t){var e=[];if(null!=t)for(var n in fl(t))e.push(n);return e}
  function $i (line 14) | function $i(t){return wl.call(t)}
  function Ji (line 14) | function Ji(t,e,n){return e=Xl(e===ot?t.length-1:e,0),function(){for(var...
  function Zi (line 14) | function Zi(t,e){return e.length<2?t:sr(t,lo(e,0,-1))}
  function Qi (line 14) | function Qi(t,e){for(var n=t.length,r=$l(e.length,n),o=Ko(t);r--;){var i...
  function tu (line 14) | function tu(t,e,n){var r=e+"";return Rf(t,Fi(r,iu(xi(r),n)))}
  function eu (line 14) | function eu(t){var e=0,n=0;return function(){var r=Jl(),o=Dt-(r-n);if(n=...
  function nu (line 14) | function nu(t,e){var n=-1,r=t.length,o=r-1;for(e=e===ot?r:e;++n<e;){var ...
  function ru (line 14) | function ru(t){if("string"==typeof t||bs(t))return t;var e=t+"";return"0...
  function ou (line 14) | function ou(t){if(null!=t){try{return ml.call(t)}catch(t){}try{return t+...
  function iu (line 14) | function iu(t,e){return c(Kt,function(n){var r="_."+n[0];e&n[1]&&!d(t,r)...
  function uu (line 14) | function uu(t){if(t instanceof b)return t.clone();var e=new o(t.__wrappe...
  function au (line 14) | function au(t,e,n){e=(n?Ui(t,e,n):e===ot)?1:Xl(Cs(e),0);var r=null==t?0:...
  function su (line 14) | function su(t){for(var e=-1,n=null==t?0:t.length,r=0,o=[];++e<n;){var i=...
  function cu (line 14) | function cu(){var t=arguments.length;if(!t)return[];for(var e=ul(t-1),n=...
  function lu (line 14) | function lu(t,e,n){var r=null==t?0:t.length;return r?(e=n||e===ot?1:Cs(e...
  function fu (line 14) | function fu(t,e,n){var r=null==t?0:t.length;return r?(e=n||e===ot?1:Cs(e...
  function pu (line 14) | function pu(t,e){return t&&t.length?Eo(t,Ti(e,3),!0,!0):[]}
  function du (line 14) | function du(t,e){return t&&t.length?Eo(t,Ti(e,3),!0):[]}
  function hu (line 14) | function hu(t,e,n,r){var o=null==t?0:t.length;return o?(n&&"number"!=typ...
  function yu (line 14) | function yu(t,e,n){var r=null==t?0:t.length;if(!r)return-1;var o=null==n...
  function _u (line 14) | function _u(t,e,n){var r=null==t?0:t.length;if(!r)return-1;var o=r-1;ret...
  function gu (line 14) | function gu(t){var e=null==t?0:t.length;return e?er(t,1):[]}
  function vu (line 14) | function vu(t){var e=null==t?0:t.length;return e?er(t,Mt):[]}
  function mu (line 14) | function mu(t,e){var n=null==t?0:t.length;return n?(e=e===ot?1:Cs(e),er(...
  function bu (line 14) | function bu(t){for(var e=-1,n=null==t?0:t.length,r={};++e<n;){var o=t[e]...
  function Eu (line 14) | function Eu(t){return t&&t.length?t[0]:ot}
  function Su (line 14) | function Su(t,e,n){var r=null==t?0:t.length;if(!r)return-1;var o=null==n...
  function wu (line 14) | function wu(t){var e=null==t?0:t.length;return e?lo(t,0,-1):[]}
  function Ou (line 14) | function Ou(t,e){return null==t?"":Vl.call(t,e)}
  function Tu (line 14) | function Tu(t){var e=null==t?0:t.length;return e?t[e-1]:ot}
  function Cu (line 14) | function Cu(t,e,n){var r=null==t?0:t.length;if(!r)return-1;var o=r;retur...
  function Iu (line 14) | function Iu(t,e){return t&&t.length?Xr(t,Cs(e)):ot}
  function Nu (line 14) | function Nu(t,e){return t&&t.length&&e&&e.length?to(t,e):t}
  function Au (line 14) | function Au(t,e,n){return t&&t.length&&e&&e.length?to(t,e,Ti(n,2)):t}
  function Du (line 14) | function Du(t,e,n){return t&&t.length&&e&&e.length?to(t,e,ot,n):t}
  function xu (line 14) | function xu(t,e){var n=[];if(!t||!t.length)return n;var r=-1,o=[],i=t.le...
  function Pu (line 14) | function Pu(t){return null==t?t:tf.call(t)}
  function ku (line 14) | function ku(t,e,n){var r=null==t?0:t.length;return r?(n&&"number"!=typeo...
  function Mu (line 14) | function Mu(t,e){return po(t,e)}
  function Ru (line 14) | function Ru(t,e,n){return ho(t,e,Ti(n,2))}
  function Fu (line 14) | function Fu(t,e){var n=null==t?0:t.length;if(n){var r=po(t,e);if(r<n&&Xa...
  function ju (line 14) | function ju(t,e){return po(t,e,!0)}
  function Lu (line 14) | function Lu(t,e,n){return ho(t,e,Ti(n,2),!0)}
  function Uu (line 14) | function Uu(t,e){var n=null==t?0:t.length;if(n){var r=po(t,e,!0)-1;if(Xa...
  function Bu (line 14) | function Bu(t){return t&&t.length?yo(t):[]}
  function Ku (line 14) | function Ku(t,e){return t&&t.length?yo(t,Ti(e,2)):[]}
  function Hu (line 14) | function Hu(t){var e=null==t?0:t.length;return e?lo(t,1,e):[]}
  function Gu (line 14) | function Gu(t,e,n){return t&&t.length?(e=n||e===ot?1:Cs(e),lo(t,0,e<0?0:...
  function zu (line 14) | function zu(t,e,n){var r=null==t?0:t.length;return r?(e=n||e===ot?1:Cs(e...
  function qu (line 14) | function qu(t,e){return t&&t.length?Eo(t,Ti(e,3),!1,!0):[]}
  function Wu (line 14) | function Wu(t,e){return t&&t.length?Eo(t,Ti(e,3)):[]}
  function Vu (line 14) | function Vu(t){return t&&t.length?vo(t):[]}
  function Yu (line 14) | function Yu(t,e){return t&&t.length?vo(t,Ti(e,2)):[]}
  function Xu (line 14) | function Xu(t,e){return e="function"==typeof e?e:ot,t&&t.length?vo(t,ot,...
  function $u (line 14) | function $u(t){if(!t||!t.length)return[];var e=0;return t=p(t,function(t...
  function Ju (line 14) | function Ju(t,e){if(!t||!t.length)return[];var n=$u(t);return null==e?n:...
  function Zu (line 14) | function Zu(t,e){return Oo(t||[],e||[],Pn)}
  function Qu (line 14) | function Qu(t,e){return Oo(t||[],e||[],so)}
  function ta (line 14) | function ta(t){var e=n(t);return e.__chain__=!0,e}
  function ea (line 14) | function ea(t,e){return e(t),t}
  function na (line 14) | function na(t,e){return e(t)}
  function ra (line 14) | function ra(){return ta(this)}
  function oa (line 14) | function oa(){return new o(this.value(),this.__chain__)}
  function ia (line 14) | function ia(){this.__values__===ot&&(this.__values__=Os(this.value()));v...
  function ua (line 14) | function ua(){return this}
  function aa (line 14) | function aa(t){for(var e,n=this;n instanceof r;){var o=uu(n);o.__index__...
  function sa (line 14) | function sa(){var t=this.__wrapped__;if(t instanceof b){var e=t;return t...
  function ca (line 14) | function ca(){return So(this.__wrapped__,this.__actions__)}
  function la (line 14) | function la(t,e,n){var r=Ep(t)?f:Vn;return n&&Ui(t,e,n)&&(e=ot),r(t,Ti(e...
  function fa (line 14) | function fa(t,e){var n=Ep(t)?p:tr;return n(t,Ti(e,3))}
  function pa (line 14) | function pa(t,e){return er(va(t,e),1)}
  function da (line 14) | function da(t,e){return er(va(t,e),Mt)}
  function ha (line 14) | function ha(t,e,n){return n=n===ot?1:Cs(n),er(va(t,e),n)}
  function ya (line 14) | function ya(t,e){var n=Ep(t)?c:mf;return n(t,Ti(e,3))}
  function _a (line 14) | function _a(t,e){var n=Ep(t)?l:bf;return n(t,Ti(e,3))}
  function ga (line 14) | function ga(t,e,n,r){t=$a(t)?t:rc(t),n=n&&!r?Cs(n):0;var o=t.length;retu...
  function va (line 14) | function va(t,e){var n=Ep(t)?y:zr;return n(t,Ti(e,3))}
  function ma (line 14) | function ma(t,e,n,r){return null==t?[]:(Ep(e)||(e=null==e?[]:[e]),n=r?ot...
  function ba (line 14) | function ba(t,e,n){var r=Ep(t)?g:D,o=arguments.length<3;return r(t,Ti(e,...
  function Ea (line 14) | function Ea(t,e,n){var r=Ep(t)?v:D,o=arguments.length<3;return r(t,Ti(e,...
  function Sa (line 14) | function Sa(t,e){var n=Ep(t)?p:tr;return n(t,Fa(Ti(e,3)))}
  function wa (line 14) | function wa(t){var e=Ep(t)?Nn:uo;return e(t)}
  function Oa (line 14) | function Oa(t,e,n){e=(n?Ui(t,e,n):e===ot)?1:Cs(e);var r=Ep(t)?An:ao;retu...
  function Ta (line 14) | function Ta(t){var e=Ep(t)?Dn:co;return e(t)}
  function Ca (line 14) | function Ca(t){if(null==t)return 0;if($a(t))return ms(t)?Q(t):t.length;v...
  function Ia (line 14) | function Ia(t,e,n){var r=Ep(t)?m:fo;return n&&Ui(t,e,n)&&(e=ot),r(t,Ti(e...
  function Na (line 14) | function Na(t,e){if("function"!=typeof e)throw new hl(st);return t=Cs(t)...
  function Aa (line 14) | function Aa(t,e,n){return e=n?ot:e,e=t&&null==e?t.length:e,di(t,Ot,ot,ot...
  function Da (line 14) | function Da(t,e){var n;if("function"!=typeof e)throw new hl(st);return t...
  function xa (line 14) | function xa(t,e,n){e=n?ot:e;var r=di(t,bt,ot,ot,ot,ot,ot,e);return r.pla...
  function Pa (line 14) | function Pa(t,e,n){e=n?ot:e;var r=di(t,Et,ot,ot,ot,ot,ot,e);return r.pla...
  function ka (line 14) | function ka(t,e,n){function r(e){var n=p,r=d;return p=d=ot,v=e,y=t.apply...
  function Ma (line 14) | function Ma(t){return di(t,Ct)}
  function Ra (line 14) | function Ra(t,e){if("function"!=typeof t||null!=e&&"function"!=typeof e)...
  function Fa (line 14) | function Fa(t){if("function"!=typeof t)throw new hl(st);return function(...
  function ja (line 14) | function ja(t){return Da(2,t)}
  function La (line 14) | function La(t,e){if("function"!=typeof t)throw new hl(st);return e=e===o...
  function Ua (line 14) | function Ua(t,e){if("function"!=typeof t)throw new hl(st);return e=null=...
  function Ba (line 14) | function Ba(t,e,n){var r=!0,o=!0;if("function"!=typeof t)throw new hl(st...
  function Ka (line 14) | function Ka(t){return Aa(t,1)}
  function Ha (line 14) | function Ha(t,e){return yp(Co(e),t)}
  function Ga (line 14) | function Ga(){if(!arguments.length)return[];var t=arguments[0];return Ep...
  function za (line 14) | function za(t){return Bn(t,ht)}
  function qa (line 14) | function qa(t,e){return e="function"==typeof e?e:ot,Bn(t,ht,e)}
  function Wa (line 14) | function Wa(t){return Bn(t,pt|ht)}
  function Va (line 14) | function Va(t,e){return e="function"==typeof e?e:ot,Bn(t,pt|ht,e)}
  function Ya (line 14) | function Ya(t,e){return null==e||Hn(t,e,zs(e))}
  function Xa (line 14) | function Xa(t,e){return t===e||t!==t&&e!==e}
  function $a (line 14) | function $a(t){return null!=t&&as(t.length)&&!is(t)}
  function Ja (line 14) | function Ja(t){return cs(t)&&$a(t)}
  function Za (line 14) | function Za(t){return t===!0||t===!1||cs(t)&&fr(t)==qt}
  function Qa (line 14) | function Qa(t){return cs(t)&&1===t.nodeType&&!gs(t)}
  function ts (line 14) | function ts(t){if(null==t)return!0;if($a(t)&&(Ep(t)||"string"==typeof t|...
  function es (line 14) | function es(t,e){return Pr(t,e)}
  function ns (line 14) | function ns(t,e,n){n="function"==typeof n?n:ot;var r=n?n(t,e):ot;return ...
  function rs (line 14) | function rs(t){if(!cs(t))return!1;var e=fr(t);return e==Yt||e==Vt||"stri...
  function os (line 14) | function os(t){return"number"==typeof t&&Wl(t)}
  function is (line 14) | function is(t){if(!ss(t))return!1;var e=fr(t);return e==Xt||e==$t||e==zt...
  function us (line 14) | function us(t){return"number"==typeof t&&t==Cs(t)}
  function as (line 14) | function as(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=Rt}
  function ss (line 14) | function ss(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}
  function cs (line 14) | function cs(t){return null!=t&&"object"==typeof t}
  function ls (line 14) | function ls(t,e){return t===e||Rr(t,e,Ii(e))}
  function fs (line 14) | function fs(t,e,n){return n="function"==typeof n?n:ot,Rr(t,e,Ii(e),n)}
  function ps (line 14) | function ps(t){return _s(t)&&t!=+t}
  function ds (line 14) | function ds(t){if(Pf(t))throw new sl(at);return Fr(t)}
  function hs (line 14) | function hs(t){return null===t}
  function ys (line 14) | function ys(t){return null==t}
  function _s (line 14) | function _s(t){return"number"==typeof t||cs(t)&&fr(t)==Zt}
  function gs (line 14) | function gs(t){if(!cs(t)||fr(t)!=te)return!1;var e=xl(t);if(null===e)ret...
  function vs (line 14) | function vs(t){return us(t)&&t>=-Rt&&t<=Rt}
  function ms (line 14) | function ms(t){return"string"==typeof t||!Ep(t)&&cs(t)&&fr(t)==ie}
  function bs (line 14) | function bs(t){return"symbol"==typeof t||cs(t)&&fr(t)==ue}
  function Es (line 14) | function Es(t){return t===ot}
  function Ss (line 14) | function Ss(t){return cs(t)&&xf(t)==se}
  function ws (line 14) | function ws(t){return cs(t)&&fr(t)==ce}
  function Os (line 14) | function Os(t){if(!t)return[];if($a(t))return ms(t)?tt(t):Ko(t);if(Fl&&t...
  function Ts (line 14) | function Ts(t){if(!t)return 0===t?t:0;if(t=Ns(t),t===Mt||t===-Mt){var e=...
  function Cs (line 14) | function Cs(t){var e=Ts(t),n=e%1;return e===e?n?e-n:e:0}
  function Is (line 14) | function Is(t){return t?Un(Cs(t),0,Lt):0}
  function Ns (line 14) | function Ns(t){if("number"==typeof t)return t;if(bs(t))return jt;if(ss(t...
  function As (line 14) | function As(t){return Ho(t,qs(t))}
  function Ds (line 14) | function Ds(t){return t?Un(Cs(t),-Rt,Rt):0===t?t:0}
  function xs (line 14) | function xs(t){return null==t?"":go(t)}
  function Ps (line 14) | function Ps(t,e){var n=vf(t);return null==e?n:Rn(n,e)}
  function ks (line 14) | function ks(t,e){return S(t,Ti(e,3),nr)}
  function Ms (line 14) | function Ms(t,e){return S(t,Ti(e,3),ir)}
  function Rs (line 14) | function Rs(t,e){return null==t?t:Ef(t,Ti(e,3),qs)}
  function Fs (line 14) | function Fs(t,e){return null==t?t:Sf(t,Ti(e,3),qs)}
  function js (line 14) | function js(t,e){return t&&nr(t,Ti(e,3))}
  function Ls (line 14) | function Ls(t,e){return t&&ir(t,Ti(e,3))}
  function Us (line 14) | function Us(t){return null==t?[]:ur(t,zs(t))}
  function Bs (line 14) | function Bs(t){return null==t?[]:ur(t,qs(t))}
  function Ks (line 14) | function Ks(t,e,n){var r=null==t?ot:sr(t,e);return r===ot?n:r}
  function Hs (line 14) | function Hs(t,e){return null!=t&&Pi(t,e,mr)}
  function Gs (line 14) | function Gs(t,e){return null!=t&&Pi(t,e,wr)}
  function zs (line 14) | function zs(t){return $a(t)?In(t):Kr(t)}
  function qs (line 14) | function qs(t){return $a(t)?In(t,!0):Hr(t)}
  function Ws (line 14) | function Ws(t,e){var n={};return e=Ti(e,3),nr(t,function(t,r,o){jn(n,e(t...
  function Vs (line 14) | function Vs(t,e){var n={};return e=Ti(e,3),nr(t,function(t,r,o){jn(n,r,e...
  function Ys (line 14) | function Ys(t,e){return Xs(t,Fa(Ti(e)))}
  function Xs (line 14) | function Xs(t,e){if(null==t)return{};var n=y(Si(t),function(t){return[t]...
  function $s (line 14) | function $s(t,e,n){e=Io(e,t);var r=-1,o=e.length;for(o||(o=1,t=ot);++r<o...
  function Js (line 14) | function Js(t,e,n){return null==t?t:so(t,e,n)}
  function Zs (line 14) | function Zs(t,e,n,r){return r="function"==typeof r?r:ot,null==t?t:so(t,e...
  function Qs (line 14) | function Qs(t,e,n){var r=Ep(t),o=r||wp(t)||Np(t);if(e=Ti(e,4),null==n){v...
  function tc (line 14) | function tc(t,e){return null==t||mo(t,e)}
  function ec (line 14) | function ec(t,e,n){return null==t?t:bo(t,e,Co(n))}
  function nc (line 14) | function nc(t,e,n,r){return r="function"==typeof r?r:ot,null==t?t:bo(t,e...
  function rc (line 14) | function rc(t){return null==t?[]:F(t,zs(t))}
  function oc (line 14) | function oc(t){return null==t?[]:F(t,qs(t))}
  function ic (line 14) | function ic(t,e,n){return n===ot&&(n=e,e=ot),n!==ot&&(n=Ns(n),n=n===n?n:...
  function uc (line 14) | function uc(t,e,n){return e=Ts(e),n===ot?(n=e,e=0):n=Ts(n),t=Ns(t),Tr(t,...
  function ac (line 14) | function ac(t,e,n){if(n&&"boolean"!=typeof n&&Ui(t,e,n)&&(e=n=ot),n===ot...
  function sc (line 14) | function sc(t){return td(xs(t).toLowerCase())}
  function cc (line 14) | function cc(t){return t=xs(t),t&&t.replace(Ze,br).replace(zn,"")}
  function lc (line 14) | function lc(t,e,n){t=xs(t),e=go(e);var r=t.length;n=n===ot?r:Un(Cs(n),0,...
  function fc (line 14) | function fc(t){return t=xs(t),t&&Ie.test(t)?t.replace(Te,Er):t}
  function pc (line 14) | function pc(t){return t=xs(t),t&&Fe.test(t)?t.replace(Re,"\\$&"):t}
  function dc (line 14) | function dc(t,e,n){t=xs(t),e=Cs(e);var r=e?Q(t):0;if(!e||r>=e)return t;v...
  function hc (line 14) | function hc(t,e,n){t=xs(t),e=Cs(e);var r=e?Q(t):0;return e&&r<e?t+ui(e-r...
  function yc (line 14) | function yc(t,e,n){t=xs(t),e=Cs(e);var r=e?Q(t):0;return e&&r<e?ui(e-r,n...
  function _c (line 14) | function _c(t,e,n){return n||null==e?e=0:e&&(e=+e),Zl(xs(t).replace(Le,"...
  function gc (line 14) | function gc(t,e,n){return e=(n?Ui(t,e,n):e===ot)?1:Cs(e),oo(xs(t),e)}
  function vc (line 14) | function vc(){var t=arguments,e=xs(t[0]);return t.length<3?e:e.replace(t...
  function mc (line 14) | function mc(t,e,n){return n&&"number"!=typeof n&&Ui(t,e,n)&&(e=n=ot),(n=...
  function bc (line 14) | function bc(t,e,n){return t=xs(t),n=null==n?0:Un(Cs(n),0,t.length),e=go(...
  function Ec (line 14) | function Ec(t,e,r){var o=n.templateSettings;r&&Ui(t,e,r)&&(e=ot),t=xs(t)...
  function Sc (line 14) | function Sc(t){return xs(t).toLowerCase()}
  function wc (line 14) | function wc(t){return xs(t).toUpperCase()}
  function Oc (line 14) | function Oc(t,e,n){if(t=xs(t),t&&(n||e===ot))return t.replace(je,"");if(...
  function Tc (line 14) | function Tc(t,e,n){if(t=xs(t),t&&(n||e===ot))return t.replace(Ue,"");if(...
  function Cc (line 14) | function Cc(t,e,n){if(t=xs(t),t&&(n||e===ot))return t.replace(Le,"");if(...
  function Ic (line 14) | function Ic(t,e){var n=It,r=Nt;if(ss(e)){var o="separator"in e?e.separat...
  function Nc (line 14) | function Nc(t){return t=xs(t),t&&Ce.test(t)?t.replace(Oe,Sr):t}
  function Ac (line 14) | function Ac(t,e,n){return t=xs(t),e=n?ot:e,e===ot?z(t)?rt(t):E(t):t.matc...
  function Dc (line 14) | function Dc(t){var e=null==t?0:t.length,n=Ti();return t=e?y(t,function(t...
  function xc (line 14) | function xc(t){return Kn(Bn(t,pt))}
  function Pc (line 14) | function Pc(t){return function(){return t}}
  function kc (line 14) | function kc(t,e){return null==t||t!==t?e:t}
  function Mc (line 14) | function Mc(t){return t}
  function Rc (line 14) | function Rc(t){return Br("function"==typeof t?t:Bn(t,pt))}
  function Fc (line 14) | function Fc(t){return qr(Bn(t,pt))}
  function jc (line 14) | function jc(t,e){return Wr(t,Bn(e,pt))}
  function Lc (line 14) | function Lc(t,e,n){var r=zs(e),o=ur(e,r);null!=n||ss(e)&&(o.length||!r.l...
  function Uc (line 14) | function Uc(){return ar._===this&&(ar._=Tl),this}
  function Bc (line 14) | function Bc(){}
  function Kc (line 14) | function Kc(t){return t=Cs(t),io(function(e){return Xr(e,t)})}
  function Hc (line 14) | function Hc(t){return Bi(t)?N(ru(t)):Qr(t)}
  function Gc (line 14) | function Gc(t){return function(e){return null==t?ot:sr(t,e)}}
  function zc (line 14) | function zc(){return[]}
  function qc (line 14) | function qc(){return!1}
  function Wc (line 14) | function Wc(){return{}}
  function Vc (line 14) | function Vc(){return""}
  function Yc (line 14) | function Yc(){return!0}
  function Xc (line 14) | function Xc(t,e){if(t=Cs(t),t<1||t>Rt)return[];var n=Lt,r=$l(t,Lt);e=Ti(...
  function $c (line 14) | function $c(t){return Ep(t)?y(t,ru):bs(t)?[t]:Ko(Ff(xs(t)))}
  function Jc (line 14) | function Jc(t){var e=++El;return xs(t)+e}
  function Zc (line 14) | function Zc(t){return t&&t.length?Yn(t,Mc,pr):ot}
  function Qc (line 14) | function Qc(t,e){return t&&t.length?Yn(t,Ti(e,2),pr):ot}
  function tl (line 14) | function tl(t){return I(t,Mc)}
  function el (line 14) | function el(t,e){return I(t,Ti(e,2))}
  function nl (line 14) | function nl(t){return t&&t.length?Yn(t,Mc,Gr):ot}
  function rl (line 14) | function rl(t,e){return t&&t.length?Yn(t,Ti(e,2),Gr):ot}
  function ol (line 14) | function ol(t){return t&&t.length?P(t,Mc):0}
  function il (line 14) | function il(t,e){return t&&t.length?P(t,Ti(e,2)):0}
  function t (line 14) | function t(){}
  function r (line 15) | function r(t){return t&&t.__esModule?t:{default:t}}
  function o (line 15) | function o(t,e){var n={};for(var r in t)e.indexOf(r)>=0||Object.prototyp...
  function i (line 15) | function i(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function u (line 15) | function u(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function a (line 15) | function a(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 15) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function r (line 15) | function r(){return i(this,r),u(this,n.apply(this,arguments))}
  function r (line 15) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 15) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 15) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function e (line 15) | function e(){return r(this,e),o(this,t.apply(this,arguments))}
  function r (line 15) | function r(t,e,n){var r=t.getSelection(),i=t.getCurrentContent(),u=r;if(...
  function n (line 15) | function n(t){return"object"==typeof t?Object.keys(t).filter(function(e)...
  function r (line 15) | function r(t){return t.replace(/\//g,"-")}
  function r (line 15) | function r(t,e){console.warn("WARNING: "+t+' will be deprecated soon!\nP...
  function n (line 15) | function n(t,e,n,r){if(t.size){var o=0;t.reduce(function(t,i,u){return e...
  function r (line 15) | function r(t,e){var n=e.getStartKey(),r=e.getStartOffset(),u=e.getEndKey...
  function n (line 15) | function n(t){return"handled"===t||t===!0}
  function t (line 15) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 15) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 15) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 15) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 15) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 15) | function e(t){r(this,e);var n=o(this,(e.__proto__||Object.getPrototypeOf...
  function r (line 15) | function r(t,e){!i.isUndefined(t)&&i.isUndefined(t["Content-Type"])&&(t[...
  function o (line 15) | function o(){var t;return"undefined"!=typeof XMLHttpRequest?t=n(56):"und...
  function r (line 15) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 15) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 15) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function e (line 15) | function e(){return r(this,e),o(this,t.apply(this,arguments))}
  function r (line 16) | function r(t){for(var e=t;e&&e!==document.documentElement;){var n=o(e);i...
  function n (line 16) | function n(t,e){var n;if(e.isCollapsed()){var o=e.getAnchorKey(),i=e.get...
  function r (line 16) | function r(t,e){if(e){var n=t.__get(e);return"MUTABLE"===n.getMutability...
  function n (line 16) | function n(t,e){var n=t.getSelection(),r=t.getCurrentContent(),o=n.getSt...
  function n (line 16) | function n(t){return t.replace(r,"")}
  function r (line 16) | function r(t,e){var n=i.get(t,e);return"auto"===n||"scroll"===n}
  function r (line 16) | function r(t){return t===f||t===p}
  function o (line 16) | function o(t){return r(t)?void 0:"production"!==e.env.NODE_ENV?c(!1,"`di...
  function i (line 16) | function i(t,n){return r(t)?void 0:"production"!==e.env.NODE_ENV?c(!1,"`...
  function u (line 16) | function u(t){d=t}
  function a (line 16) | function a(){u(f)}
  function s (line 16) | function s(){return d||this.initGlobalDir(),d?void 0:"production"!==e.en...
  function r (line 16) | function r(t){var e=o(t.ownerDocument||t.document);t.Window&&t instanceo...
  function t (line 16) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 16) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 16) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 16) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 16) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 16) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function n (line 16) | function n(t){"undefined"!=typeof console&&"function"==typeof console.er...
  function n (line 16) | function n(t){this.message=t}
  function r (line 16) | function r(t,e){var n=[],r=t.map(function(t){return t.getStyle()}).toLis...
  function o (line 16) | function o(t,e){return t===e}
  function r (line 16) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 16) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 16) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function u (line 16) | function u(t,e){return t.getAnchorKey()===e||t.getFocusKey()===e}
  function n (line 16) | function n(){return r(this,n),o(this,t.apply(this,arguments))}
  function r (line 16) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 16) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 16) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function e (line 16) | function e(){return r(this,e),o(this,t.apply(this,arguments))}
  function r (line 16) | function r(t,e){var n=e?f.exec(t):c.exec(t);return n?n[0]:t}
  function r (line 16) | function r(){return{text:"",inlines:[],entities:[],blocks:[]}}
  function o (line 16) | function o(t){var e=new Array(1);return t&&(e[0]=t),{text:R,inlines:[k()...
  function i (line 16) | function i(){return{text:"\n",inlines:[k()],entities:new Array(1),blocks...
  function u (line 16) | function u(t,e){return{text:"\r",inlines:[k()],entities:new Array(1),blo...
  function a (line 16) | function a(t,e){return"li"===t?"ol"===e?"ordered-list-item":"unordered-l...
  function s (line 16) | function s(t){var e=t.get("unstyled").element,n=new x([]);return t.forEa...
  function c (line 16) | function c(t,e,n){for(var r=0;r<n.length;r++){var o=n[r](t,e);if(o)retur...
  function l (line 16) | function l(t,e,n){var r=n.filter(function(e){return e.element===t||e.wra...
  function f (line 16) | function f(t,e,n){var r=z[t];return r?n=n.add(r).toOrderedSet():e instan...
  function p (line 16) | function p(t,e){var n=t.text.slice(-1),r=e.text.slice(0,1);if("\r"===n&&...
  function d (line 16) | function d(t,e){return e.some(function(e){return t.indexOf("<"+e)!==-1})}
  function h (line 16) | function h(t){t instanceof HTMLAnchorElement?void 0:"production"!==e.env...
  function y (line 16) | function y(t,e,n,a,s,c,d,_,g){var m=e.nodeName.toLowerCase(),b=!1,E="uns...
  function _ (line 16) | function _(t,e,n,r){t=t.trim().replace(j,"").replace(U,R).replace(B,"")....
  function g (line 16) | function g(t){var e=arguments.length<=1||void 0===arguments[1]?C:argumen...
  function r (line 16) | function r(t){return f&&t.altKey||y(t)}
  function o (line 16) | function o(t){return h(t)?t.shiftKey?"redo":"undo":null}
  function i (line 16) | function i(t){return p&&t.shiftKey?null:r(t)?"delete-word":"delete"}
  function u (line 16) | function u(t){return h(t)&&f?"backspace-to-start-of-line":r(t)?"backspac...
  function a (line 16) | function a(t){switch(t.keyCode){case 66:return h(t)?"bold":null;case 68:...
  function r (line 16) | function r(t,e,n,r,o,i){var a=n.nodeType===Node.TEXT_NODE,c=o.nodeType==...
  function o (line 16) | function o(t){for(;t.firstChild&&c(t.firstChild);)t=t.firstChild;return t}
  function i (line 16) | function i(t){for(;t.lastChild&&c(t.lastChild);)t=t.lastChild;return t}
  function u (line 16) | function u(t,n,r){var u=n,l=s(u);if(null!=l||t&&(t===u||t.firstChild===u...
  function a (line 16) | function a(t){var e=t.textContent;return"\n"===e?0:e.length}
  function r (line 16) | function r(t){var e=t.getSelection();return e.isCollapsed()?null:o(t.get...
  function r (line 16) | function r(t){for(var n=t.cloneRange(),r=[],o=t.endContainer;null!=o;o=o...
  function r (line 16) | function r(t){var n,r=null;return!u&&document.implementation&&document.i...
  function n (line 16) | function n(t){if(t instanceof Element){var e=t.getAttribute("data-offset...
  function n (line 16) | function n(t,e){var n=0,o=[];t.forEach(function(i){r(i,function(r){n++,r...
  function r (line 16) | function r(t,n){if(!e.FileReader||t.type&&!(t.type in i))return void n("...
  function r (line 16) | function r(t,n,r,u,a){var s=i(t.getSelection());if(!("production"===e.en...
  function n (line 16) | function n(t,e,n){if(n===t.count())e.forEach(function(e){t=t.push(e)});e...
  function n (line 16) | function n(t){var e=t.getSelection(),n=e.getAnchorKey(),r=t.getBlockTree...
  function n (line 16) | function n(t,e){var n,r=t.getSelection(),o=r.getStartKey(),i=r.getStartO...
  function r (line 16) | function r(t,e){var n=t.getBlockMap(),r=t.getEntityMap(),o={},u=e.getSta...
  function o (line 16) | function o(t,n,r){var o;return a(t,function(t,e){return t.getEntity()===...
  function i (line 16) | function i(t,e,n){var r=e.getCharacterList(),i=n>0?r.get(n-1):void 0,a=n...
  function r (line 16) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 16) | function o(t){if("file"==t.kind)return t.getAsFile()}
  function t (line 16) | function t(e){r(this,t),this.data=e,this.types=e.types?u(e.types):[]}
  function n (line 16) | function n(t,e){return!!e&&(t===e.documentElement||t===e.body)}
  function r (line 16) | function r(t){var e=p.exec(t);return null==e?null:e[0]}
  function o (line 16) | function o(t){var e=r(t);return null==e?c.NEUTRAL:d.exec(e)?c.RTL:c.LTR}
  function i (line 16) | function i(t,e){if(e=e||c.NEUTRAL,!t.length)return e;var n=o(t);return n...
  function u (line 16) | function u(t,n){return n||(n=c.getGlobalDir()),c.isStrong(n)?void 0:"pro...
  function a (line 16) | function a(t,e){return u(t,e)===c.LTR}
  function s (line 16) | function s(t,e){return u(t,e)===c.RTL}
  function r (line 17) | function r(t,e){return!(!t||!e)&&(t===e||!o(t)&&(o(e)?r(t,e.parentNode):...
  function n (line 17) | function n(t){if(t=t||("undefined"!=typeof document?document:void 0),"un...
  function t (line 17) | function t(t){return t&&t.__esModule?t:{default:t}}
  function t (line 17) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 17) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 17) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 17) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 17) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 17) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 17) | function t(t){return t&&t.__esModule?t:{default:t}}
  function t (line 17) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 17) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 17) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 17) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 17) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 17) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 17) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 17) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 17) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 17) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 17) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 17) | function e(t){r(this,e);var n=o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 17) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 17) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 17) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 17) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 17) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 17) | function e(){r(this,e);var t=o(this,(e.__proto__||Object.getPrototypeOf(...
  function t (line 17) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 17) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 17) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 17) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 17) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 17) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function r (line 17) | function r(t){return t&&t.__esModule?t:{default:t}}
  function o (line 17) | function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function i (line 17) | function i(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function u (line 17) | function u(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function a (line 17) | function a(t,e){var n={};for(var r in t)e.indexOf(r)>=0||Object.prototyp...
  function s (line 17) | function s(){}
  function c (line 17) | function c(t,e){var n={run:function(r){try{var o=t(e.getState(),r);(o!==...
  function l (line 17) | function l(e){var n,r,l=arguments.length>1&&void 0!==arguments[1]?argume...
  function r (line 17) | function r(t){return t&&t.__esModule?t:{default:t}}
  function o (line 17) | function o(t){return function(e,n){function r(){return o}var o=t(e,n);re...
  function i (line 17) | function i(t){return null!==t.dependsOnOwnProps&&void 0!==t.dependsOnOwn...
  function u (line 17) | function u(e,n){return function(r,o){var u=o.displayName,a=function(t,e)...
  function r (line 17) | function r(t){return t&&t.__esModule?t:{default:t}}
  function r (line 17) | function r(t){return t&&t.__esModule?t:{default:t}}
  function o (line 17) | function o(t,e,n){(0,u.default)(t)||(0,s.default)(n+"() in "+e+" must re...
  function r (line 17) | function r(t){var e=new u(t),n=i(u.prototype.request,e);return o.extend(...
  function r (line 17) | function r(t){if("function"!=typeof t)throw new TypeError("executor must...
  function r (line 17) | function r(t){this.defaults=t,this.interceptors={request:new u,response:...
  function r (line 17) | function r(){this.handlers=[]}
  function r (line 17) | function r(t){t.cancelToken&&t.cancelToken.throwIfRequested()}
  function n (line 17) | function n(){this.message="String contains an invalid character"}
  function r (line 17) | function r(t){for(var e,r,i=String(t),u="",a=0,s=o;i.charAt(0|a)||(s="="...
  function r (line 17) | function r(t){return encodeURIComponent(t).replace(/%40/gi,"@").replace(...
  function t (line 18) | function t(t){var e=t;return n&&(o.setAttribute("href",e),e=o.href),o.se...
  function r (line 18) | function r(t){return t}
  function o (line 18) | function o(t,n,o){function f(t,n,r){for(var o in n)n.hasOwnProperty(o)&&...
  function r (line 18) | function r(t){return null===t||void 0===t}
  function o (line 18) | function o(t){return!(!t||"object"!=typeof t||"number"!=typeof t.length)...
  function i (line 18) | function i(t,e,n){var i,l;if(r(t)||r(e))return!1;if(t.prototype!==e.prot...
  function n (line 18) | function n(t){return"[object Arguments]"==Object.prototype.toString.call...
  function r (line 18) | function r(t){return t&&"object"==typeof t&&"number"==typeof t.length&&O...
  function n (line 18) | function n(t){var e=[];for(var n in t)e.push(n);return e}
  function r (line 18) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 18) | function o(t,e,n){for(var r=e;r<n;r++)if(null!=t[r])return!1;return!0}
  function i (line 18) | function i(t,e,n,r){for(var o=e;o<n;o++)t[o]=r}
  function t (line 18) | function t(e){r(this,t),this._decorators=e.slice()}
  function r (line 18) | function r(t,e,n,r){var i=t.getBlockMap(),a=e.getStartKey(),s=e.getStart...
  function r (line 18) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 18) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 18) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function n (line 18) | function n(e){r(this,n);var i=o(this,t.call(this,e));return i._blockSele...
  function r (line 18) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 18) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 18) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function u (line 18) | function u(t,e,n,r){return p({"public/DraftStyleDefault/unorderedListIte...
  function e (line 18) | function e(){return r(this,e),o(this,t.apply(this,arguments))}
  function r (line 18) | function r(t,e){var n=null,r=null;if("function"==typeof document.caretRa...
  function o (line 18) | function o(t,e){var n=a.moveText(t.getCurrentContent(),t.getSelection(),...
  function i (line 18) | function i(t,e,n){var r=a.insertText(t.getCurrentContent(),e,n,t.getCurr...
  function r (line 18) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 18) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 18) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function n (line 18) | function n(){return r(this,n),o(this,t.apply(this,arguments))}
  function r (line 18) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 18) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 18) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function e (line 18) | function e(){return r(this,e),o(this,t.apply(this,arguments))}
  function r (line 18) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 18) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 18) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function u (line 18) | function u(t){return f?"\n"===t.textContent:"BR"===t.tagName}
  function n (line 18) | function n(e){r(this,n);var i=o(this,t.call(this,e));return i._forceFlag...
  function n (line 19) | function n(t,e,n,r){var o=e.getStartKey(),i=e.getEndKey(),u=t.getBlockMa...
  function r (line 19) | function r(t,e,n,r){for(var i=t.getCharacterList();e<n;)i=i.set(e,o.appl...
  function r (line 19) | function r(t,e,n){var r=t.getBlockMap(),u=e.getStartKey(),a=e.getStartOf...
  function r (line 19) | function r(t){var e=0,n={},r=[];t.getBlockMap().forEach(function(t,a){t....
  function r (line 19) | function r(t){var e=t.blocks,n=t.entityMap,r={};Object.keys(n).forEach(f...
  function r (line 19) | function r(t,e){var n=t.map(function(t,n){var r=e[n];return o.create({st...
  function r (line 19) | function r(t,e){var n=Array(t.length).fill(null);return e&&e.forEach(fun...
  function r (line 19) | function r(t,e){var n=Array(t.length).fill(s);return e&&e.forEach(functi...
  function r (line 19) | function r(t){return g&&(t==y||t==_)}
  function o (line 19) | function o(t,e,n,r){var o=a.replaceText(t.getCurrentContent(),t.getSelec...
  function i (line 19) | function i(t,e){void 0!==t._pendingStateFromBeforeInput&&(t.update(t._pe...
  function r (line 19) | function r(t,n){a&&u()===document.body&&e.getSelection().removeAllRanges...
  function r (line 19) | function r(t,e){t.setMode("composite"),t.update(o.set(t._latestEditorSta...
  function r (line 19) | function r(t,e){var n=t._latestEditorState,r=n.getSelection();return r.i...
  function r (line 19) | function r(t,e){var n=t._latestEditorState,r=n.getSelection();if(r.isCol...
  function o (line 19) | function o(t){var e=i.removeRange(t.getCurrentContent(),t.getSelection()...
  function n (line 19) | function n(t,e){t._internalDrag=!1,t.setMode("drag"),e.preventDefault()}
  function n (line 19) | function n(t){t._internalDrag=!0,t.setMode("drag")}
  function r (line 19) | function r(t,e){var n=t._latestEditorState,r=n.getSelection();if(!r.getH...
  function r (line 19) | function r(t){void 0!==t._pendingStateFromBeforeInput&&(t.update(t._pend...
  function r (line 19) | function r(t,e){switch(t){case"redo":return u.redo(e);case"delete":retur...
  function o (line 19) | function o(t,e){var n=e.which,o=t._latestEditorState;switch(n){case s.RE...
  function r (line 19) | function r(t,e){e.preventDefault();var n=new s(e.clipboardData);if(!n.is...
  function o (line 19) | function o(t,e,n){var r=c.replaceWithFragment(t.getCurrentContent(),t.ge...
  function i (line 19) | function i(t,e){return t.length===e.size&&e.valueSeq().every(function(e,...
  function r (line 19) | function r(t){if(!t._blockSelectEvents&&t._latestEditorState===t.props.e...
  function r (line 19) | function r(t,e){var n=[];return t.findEntityRanges(function(t){return!!t...
  function r (line 19) | function r(t,e,n){var r=[],o=e.map(function(t){return t.has(n)}).toList(...
  function o (line 19) | function o(t){var e=t.getCharacterList().map(function(t){return t.getSty...
  function r (line 19) | function r(t){var n=getComputedStyle(t),r=document.createElement("div");...
  function o (line 19) | function o(t,e){for(var n=1/0,r=1/0,o=-(1/0),i=-(1/0),u=0;u<t.length;u++...
  function i (line 19) | function i(t){switch(t.nodeType){case Node.DOCUMENT_TYPE_NODE:return 0;c...
  function u (line 19) | function u(t){t.collapsed?void 0:"production"!==e.env.NODE_ENV?c(!1,"exp...
  function r (line 19) | function r(t,e,n,r,i){var u=r.getStartOffset(),a=r.getEndOffset(),s=e.ge...
  function o (line 19) | function o(t,n,r,o,s,c,l){var f=r.getStartOffset(),p=r.getEndOffset(),d=...
  function r (line 19) | function r(t,n){var r=e.getSelection();return 0===r.rangeCount?{selectio...
  function r (line 19) | function r(t){var e=o(t),n=0,r=0,i=0,u=0;if(e.length){if(e.length>1&&0==...
  function r (line 19) | function r(t,n){var r=[];return t.findEntityRanges(function(t){return t....
  function r (line 19) | function r(t){var e=t.getSelection();if(!e.rangeCount)return null;var n=...
  function r (line 19) | function r(t,n,r){n.isCollapsed()?void 0:"production"!==e.env.NODE_ENV?a...
  function r (line 19) | function r(t,n,r,o){n.isCollapsed()?void 0:"production"!==e.env.NODE_ENV...
  function r (line 19) | function r(t){var n=s(t,function(t){var n=t.getSelection();if(n.isCollap...
  function r (line 19) | function r(t){var e=a(t,function(t){var e=t.getSelection(),n=e.getStartO...
  function r (line 19) | function r(t){var e=a(t,function(t){var e=t.getSelection(),n=e.getStartO...
  function r (line 19) | function r(t){var e=o.splitBlock(t.getCurrentContent(),t.getSelection())...
  function r (line 19) | function r(t){var e=t.getSelection(),n=e.getEndKey(),r=t.getCurrentConte...
  function r (line 19) | function r(t){var e=t.getSelection(),n=e.getStartKey();return o.set(t,{s...
  function r (line 19) | function r(t){var e=a(t,function(t){var e=t.getSelection(),n=t.getCurren...
  function r (line 19) | function r(t){var e=a(t,function(t){var e=t.getSelection(),n=t.getCurren...
  function r (line 19) | function r(t){var e=t.getSelection();if(!e.isCollapsed())return t;var n=...
  function r (line 19) | function r(t,e,n){var r=o.undo(e);if("spellcheck-change"===e.getLastChan...
  function r (line 19) | function r(t,e,n){var r=e.getStartKey(),o=e.getEndKey(),u=t.getBlockMap(...
  function r (line 19) | function r(t,n,r,i){n.getKey()===r.getKey()?"production"!==e.env.NODE_EN...
  function r (line 19) | function r(t,e){if(e.isCollapsed())return t;var n,r=t.getBlockMap(),u=e....
  function o (line 19) | function o(t,e,n){if(0===e)for(;e<n;)t=t.shift(),e++;else if(n===t.count...
  function o (line 19) | function o(t){if(!t)return"[empty]";var n=i(t);return n.nodeType===Node....
  function i (line 19) | function i(t){if(t.nodeType===Node.TEXT_NODE){var e=t.textContent.length...
  function u (line 19) | function u(t){for(var e=t;e;){if(e instanceof Element&&e.hasAttribute("c...
  function a (line 19) | function a(t){return null===t.nodeValue?t.childNodes.length:t.nodeValue....
  function s (line 19) | function s(t,e,n,o,i){if(p(document.documentElement,e)){var u=r.getSelec...
  function c (line 19) | function c(t,e,n,r){if(t.extend&&p(d(),e))n>a(e)&&f.logSelectionStateFai...
  function l (line 20) | function l(t,e,n,r){var o=document.createRange();n>a(e)&&f.logSelectionS...
  function r (line 20) | function r(t,n){n.isCollapsed()?void 0:"production"!==e.env.NODE_ENV?u(!...
  function n (line 20) | function n(t){return t.split(r)}
  function n (line 25) | function n(t){return t.split("/")}
  function n (line 25) | function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function t (line 25) | function t(e){n(this,t),this._uri=e}
  function r (line 25) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function t (line 25) | function t(n){r(this,t),n?i.isStrong(n)?void 0:"production"!==e.env.NODE...
  function r (line 25) | function r(t){return a[t]||t}
  function o (line 25) | function o(t){if(!t)return{major:"",minor:""};var e=t.split(".");return{...
  function r (line 25) | function r(t,e){var n=t.split(S);return n.length>1?n.some(function(t){re...
  function o (line 25) | function o(t,n){var r=t.split(w);if(r.length>0&&r.length<=2?void 0:"prod...
  function i (line 25) | function i(t,e){if(t=t.trim(),""===t)return!0;var n=e.split(E),r=p(t),o=...
  function u (line 25) | function u(t,e){return m(t,e)===-1}
  function a (line 25) | function a(t,e){var n=m(t,e);return n===-1||0===n}
  function s (line 25) | function s(t,e){return 0===m(t,e)}
  function c (line 25) | function c(t,e){var n=m(t,e);return 1===n||0===n}
  function l (line 25) | function l(t,e){return 1===m(t,e)}
  function f (line 25) | function f(t,e){var n=e.slice(),r=e.slice();r.length>1&&r.pop();var o=r....
  function p (line 25) | function p(t){var n=t.split(E),r=n[0].match(O);return r?void 0:"producti...
  function d (line 25) | function d(t){return!isNaN(t)&&isFinite(t)}
  function h (line 25) | function h(t){return!p(t).modifier}
  function y (line 25) | function y(t,e){for(var n=t.length;n<e;n++)t[n]="0"}
  function _ (line 25) | function _(t,e){t=t.slice(),e=e.slice(),y(t,e.length);for(var n=0;n<e.le...
  function g (line 25) | function g(t,e){var n=t.match(T)[1],r=e.match(T)[1],o=parseInt(n,10),i=p...
  function v (line 25) | function v(t,n){return typeof t!=typeof n?"production"!==e.env.NODE_ENV?...
  function m (line 25) | function m(t,e){for(var n=_(t,e),r=n[0],o=n[1],i=0;i<o.length;i++){var u...
  function n (line 25) | function n(t){return t.replace(r,function(t,e){return e.toUpperCase()})}
  function r (line 25) | function r(t){var n=t.length;if(Array.isArray(t)||"object"!=typeof t&&"f...
  function o (line 25) | function o(t){return!!t&&("object"==typeof t||"function"==typeof t)&&"le...
  function i (line 25) | function i(t){return o(t)?Array.isArray(t)?t.slice():r(t):[t]}
  function n (line 25) | function n(t){return t=t||document,r||"CSS1Compat"!==t.compatMode?t.body...
  function r (line 25) | function r(t){var e=o(t);return{x:e.left,y:e.top,width:e.right-e.left,he...
  function r (line 25) | function r(t){var e=t.ownerDocument.documentElement;if(!("getBoundingCli...
  function r (line 25) | function r(t){return null==t?t:String(t)}
  function o (line 25) | function o(t,e){var n=void 0;if(window.getComputedStyle&&(n=window.getCo...
  function n (line 25) | function n(t){return t.Window&&t instanceof t.Window?{x:t.pageXOffset||t...
  function n (line 25) | function n(){var t=void 0;return document.documentElement&&(t=document.d...
  function r (line 25) | function r(){var t=void 0;return document.documentElement&&(t=document.d...
  function o (line 25) | function o(){return{width:window.innerWidth||n(),height:window.innerHeig...
  function n (line 25) | function n(t){return t.replace(r,"-$1").toLowerCase()}
  function n (line 25) | function n(t){var e=t?t.ownerDocument||t:document,n=e.defaultView||windo...
  function r (line 25) | function r(t){return o(t)&&3==t.nodeType}
  function n (line 25) | function n(t){t||(t="");var e=void 0,n=arguments.length;if(n>1)for(var r...
  function n (line 25) | function n(t,e,n){if(!t)return null;var o={};for(var i in t)r.call(t,i)&...
  function n (line 25) | function n(t){var e={};return function(n){return e.hasOwnProperty(n)||(e...
  function r (line 25) | function r(t){return null==t?void 0===t?s:a:c&&c in Object(t)?i(t):u(t)}
  function r (line 25) | function r(t){var e=u.call(t,s),n=t[s];try{t[s]=void 0;var r=!0}catch(t)...
  function n (line 25) | function n(t){return o.call(t)}
  function n (line 25) | function n(t,e){return function(n){return t(e(n))}}
  function n (line 25) | function n(t){return null!=t&&"object"==typeof t}
  function r (line 25) | function r(t){if(!u(t)||o(t)!=a)return!1;var e=i(t);if(null===e)return!0...
  function r (line 25) | function r(t,n,r,s,c){if("production"!==e.env.NODE_ENV)for(var l in t)if...
  function t (line 25) | function t(t,e,n,r,u,a){a!==i&&o(!1,"Calling PropTypes validators direct...
  function e (line 25) | function e(){return t}
  function s (line 25) | function s(t){var e=t&&(N&&t[N]||t[A]);if("function"==typeof e)return e}
  function c (line 25) | function c(t,e){return t===e?0!==t||1/t===1/e:t!==t&&e!==e}
  function l (line 25) | function l(t){this.message=t,this.stack=""}
  function f (line 25) | function f(t){function r(r,c,f,p,d,h,y){if(p=p||D,h=h||f,y!==u)if(n)o(!1...
  function p (line 25) | function p(t){function e(e,n,r,o,i,u){var a=e[n],s=O(a);if(s!==t){var c=...
  function d (line 25) | function d(){return f(r.thatReturnsNull)}
  function h (line 25) | function h(t){function e(e,n,r,o,i){if("function"!=typeof t)return new l...
  function y (line 25) | function y(){function e(e,n,r,o,i){var u=e[n];if(!t(u)){var a=O(u);retur...
  function _ (line 25) | function _(t){function e(e,n,r,o,i){if(!(e[n]instanceof t)){var u=t.name...
  function g (line 25) | function g(t){function n(e,n,r,o,i){for(var u=e[n],a=0;a<t.length;a++)if...
  function v (line 25) | function v(t){function e(e,n,r,o,i){if("function"!=typeof t)return new l...
  function m (line 25) | function m(t){function n(e,n,r,o,i){for(var a=0;a<t.length;a++){var s=t[...
  function b (line 25) | function b(){function t(t,e,n,r,o){return S(t[e])?null:new l("Invalid "+...
  function E (line 25) | function E(t){function e(e,n,r,o,i){var a=e[n],s=O(a);if("object"!==s)re...
  function S (line 25) | function S(e){switch(typeof e){case"number":case"string":case"undefined"...
  function w (line 25) | function w(t,e){return"symbol"===t||("Symbol"===e["@@toStringTag"]||"fun...
  function O (line 25) | function O(t){var e=typeof t;return Array.isArray(t)?"array":t instanceo...
  function T (line 25) | function T(t){if("undefined"==typeof t||null===t)return""+t;var e=O(t);i...
  function C (line 25) | function C(t){var e=T(t);switch(e){case"array":case"object":return"an "+...
  function I (line 25) | function I(t){return t.constructor&&t.constructor.name?t.constructor.nam...
  function r (line 25) | function r(t){return t&&t.__esModule?t:{default:t}}
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 26) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 26) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 26) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 26) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 26) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 26) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 26) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 26) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 26) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 26) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 26) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 26) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 26) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 26) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 26) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 26) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 26) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 26) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 26) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 26) | function e(t){r(this,e);var n=o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 26) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 26) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 26) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 26) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 26) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 26) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 26) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 26) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 26) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 26) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 26) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 26) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 26) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 26) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 26) | function e(t){r(this,e);var n=o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 26) | function r(t){if(Array.isArray(t)){for(var e=0,n=Array(t.length);e<t.len...
  function o (line 26) | function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function i (line 26) | function i(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function u (line 26) | function u(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 26) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 26) | function e(t){o(this,e);var n=i(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 26) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 26) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 26) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 26) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 26) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 26) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 26) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 26) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 26) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 26) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 26) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 26) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 26) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 26) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 26) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 26) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 26) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 26) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 27) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 27) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 27) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 27) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 27) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 27) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 27) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 27) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 27) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 27) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 27) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 27) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 27) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 27) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 27) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 27) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 27) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 27) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 27) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 27) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 27) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 27) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 27) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 27) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 27) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 27) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 27) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 27) | function t(t){return t&&t.__esModule?t:{default:t}}
  function t (line 27) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 27) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 27) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 27) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 27) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 27) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 27) | function t(t){return t&&t.__esModule?t:{default:t}}
  function t (line 27) | function t(t){return t&&t.__esModule?t:{default:t}}
  function t (line 27) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 27) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 27) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 27) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 27) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 27) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 27) | function t(t){return t&&t.__esModule?t:{default:t}}
  function t (line 27) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 27) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 27) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 27) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 27) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 27) | function e(t){r(this,e);var n=o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 28) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 28) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 28) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 28) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 28) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 28) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 28) | function t(t){return t&&t.__esModule?t:{default:t}}
  function t (line 28) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 28) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 28) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 28) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 28) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 28) | function e(t){r(this,e);var n=o(this,(e.__proto__||Object.getPrototypeOf...
  function t (line 28) | function t(t){return t&&t.__esModule?t:{default:t}}
  function t (line 28) | function t(t){return t&&t.__esModule?t:{default:t}}
  function r (line 28) | function r(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function o (line 28) | function o(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function i (line 28) | function i(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function t (line 28) | function t(t,e){for(var n=0;n<e.length;n++){var r=e[n];r.enumerable=r.en...
  function e (line 28) | function e(){return r(this,e),o(this,(e.__proto__||Object.getPrototypeOf...
  function u (line 28) | function u(t,e,n,r){return function(t,o){var i=r({statics:{getClass:func...
  function a (line 28) | function a(i,u){r=[n(1),n(20),n(124)],o=function(t,e,n){return n||(n=t.c...
  function r (line 28) | function r(t){return t&&t.__esModule?t:{default:t}}
  function o (line 28) | function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function i (line 28) | function i(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function u (line 28) | function u(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function a (line 28) | function a(){y||(y=!0,(0,h.default)("<Provider> does not support changin...
  function s (line 28) | function s(){var e,n=arguments.length>0&&void 0!==arguments[0]?arguments...
  function r (line 28) | function r(t){return t&&t.__esModule?t:{default:t}}
  function o (line 28) | function o(t,e){var n={};for(var r in t)e.indexOf(r)>=0||Object.prototyp...
  function i (line 28) | function i(t,e,n){for(var r=e.length-1;r>=0;r--){var o=e[r](t);if(o)retu...
  function u (line 28) | function u(t,e){return t===e}
  function a (line 28) | function a(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0...
  function r (line 28) | function r(t){return"function"==typeof t?(0,a.wrapMapToPropsFunc)(t,"map...
  function o (line 28) | function o(t){return t?void 0:(0,a.wrapMapToPropsConstant)(function(t){r...
  function i (line 28) | function i(t){return t&&"object"==typeof t?(0,a.wrapMapToPropsConstant)(...
  function r (line 28) | function r(t){return"function"==typeof t?(0,i.wrapMapToPropsFunc)(t,"map...
  function o (line 28) | function o(t){return t?void 0:(0,i.wrapMapToPropsConstant)(function(){re...
  function r (line 28) | function r(t){return t&&t.__esModule?t:{default:t}}
  function o (line 28) | function o(t,e,n){return s({},n,t,e)}
  function i (line 28) | function i(e){return function(n,r){var o=r.displayName,i=r.pure,u=r.areM...
  function u (line 28) | function u(t){return"function"==typeof t?i(t):void 0}
  function a (line 28) | function a(t){return t?void 0:function(){return o}}
  function r (line 28) | function r(t){return t&&t.__esModule?t:{default:t}}
  function o (line 28) | function o(t,e){var n={};for(var r in t)e.indexOf(r)>=0||Object.prototyp...
  function i (line 28) | function i(t,e,n,r){return function(o,i){return n(t(o,i),e(r,i),i)}}
  function u (line 28) | function u(t,e,n,r,o){function i(o,i){return h=o,y=i,_=t(h,y),g=e(r,y),v...
  function a (line 28) | function a(e,n){var r=n.initMapStateToProps,a=n.initMapDispatchToProps,s...
  function r (line 28) | function r(t){return t&&t.__esModule?t:{default:t}}
  function o (line 28) | function o(t,e,n){if(!t)throw new Error("Unexpected value for "+e+" in "...
  function i (line 28) | function i(t,e,n,r){o(t,"mapStateToProps",r),o(e,"mapDispatchToProps",r)...
  function n (line 28) | function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function r (line 28) | function r(){var t=[],e=[];return{clear:function(){e=o,t=o},notify:funct...
  function t (line 28) | function t(e,r,o){n(this,t),this.store=e,this.parentSub=r,this.onStateCh...
  function n (line 28) | function n(t,e){return t===e?0!==t||0!==e||1/t===1/e:t!==t&&e!==e}
  function r (line 28) | function r(t,e){if(n(t,e))return!0;if("object"!=typeof t||null===t||"obj...
  function r (line 28) | function r(t){return t&&t.__esModule?t:{default:t}}
  function o (line 28) | function o(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a ...
  function i (line 28) | function i(t,e){if(!t)throw new ReferenceError("this hasn't been initial...
  function u (line 28) | function u(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("S...
  function r (line 28) | function r(t){return t.displayName||t.name||"Component"}
  function f (line 29) | function f(){h=t(d.map(function(t){return t.props})),y.canUseDOM?e(h):n&...
  function e (line 29) | function e(){return o(this,e),i(this,t.apply(this,arguments))}
  function n (line 29) | function n(t){return function(e){var n=e.dispatch,r=e.getState;return fu...
  function r (line 29) | function r(t){"function"!=typeof t&&(t=new Function(""+t));for(var e=new...
  function o (line 29) | function o(t){delete y[t]}
  function i (line 29) | function i(t){var e=t.callback,r=t.args;switch(r.length){case 0:e();brea...
  function u (line 29) | function u(t){if(_)setTimeout(u,0,t);else{var e=y[t];if(e){_=!0;try{i(e)...
  function a (line 29) | function a(){d=function(t){e.nextTick(function(){u(t)})}}
  function s (line 29) | function s(){if(t.postMessage&&!t.importScripts){var e=!0,n=t.onmessage;...
  function c (line 29) | function c(){var e="setImmediate$"+Math.random()+"$",n=function(n){n.sou...
  function l (line 29) | function l(){var t=new MessageChannel;t.port1.onmessage=function(t){var ...
  function f (line 29) | function f(){var t=g.documentElement;d=function(e){var n=g.createElement...
  function p (line 29) | function p(){d=function(t){setTimeout(u,0,t)}}
Condensed preview — 126 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (677K chars).
[
  {
    "path": ".babelrc",
    "chars": 56,
    "preview": "{\n  \"presets\": [\n    \"es2015\", \"stage-2\", \"react\"\n  ]\n}\n"
  },
  {
    "path": ".editorconfig",
    "chars": 167,
    "preview": "[*]\nindent_style = space\nend_of_line = lf\nindent_size = 2\ncharset = utf-8\ntrim_trailing_whitespace = true\n\n[*.md]\nmax_li"
  },
  {
    "path": ".eslintrc",
    "chars": 427,
    "preview": "{\n  \"ecmaFeatures\": {\n    \"jsx\": true,\n    \"modules\": true\n  },\n  \"env\": {\n    \"browser\": true,\n    \"node\": true\n  },\n  "
  },
  {
    "path": ".gitignore",
    "chars": 142,
    "preview": "# dependency\nnode_modules\n\n# yarn\nyarn.lock\n\n# npm cache\n.npm\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# coverages\ncoverage\n\n#"
  },
  {
    "path": "LICENSE",
    "chars": 1072,
    "preview": "MIT License\n\nCopyright (c) 2016 Provash Shoumma\n\nPermission is hereby granted, free of charge, to any person obtaining a"
  },
  {
    "path": "README.md",
    "chars": 4273,
    "preview": "![logo](./docs/design_assets/logo.png)\n\n\n# ReForum\nA minimal forum application built with the following technologies:\n* "
  },
  {
    "path": "backend/dev.js",
    "chars": 708,
    "preview": "/**\n * module dependencies for development\n */\nconst webpack = require('webpack');\nconst webpackDevMiddleware = require("
  },
  {
    "path": "backend/entities/admin/api.js",
    "chars": 2138,
    "preview": "// controllers\nconst getAdminDashInfo = require('./controller').getAdminDashInfo;\nconst createForum = require('./control"
  },
  {
    "path": "backend/entities/admin/controller.js",
    "chars": 4772,
    "preview": "const waterfall = require('async/waterfall');\n\n// models\nconst Discussion = require('../discussion/model');\nconst Opinio"
  },
  {
    "path": "backend/entities/discussion/api.js",
    "chars": 2026,
    "preview": "// discussion controllers\nconst getDiscussion = require('./controller').getDiscussion;\nconst createDiscussion = require("
  },
  {
    "path": "backend/entities/discussion/controller.js",
    "chars": 4193,
    "preview": "const generateDiscussionSlug = require('../../utilities/tools').generateDiscussionSlug;\nconst getAllOpinions = require('"
  },
  {
    "path": "backend/entities/discussion/model.js",
    "chars": 502,
    "preview": "/**\n * discussion model\n */\nconst mongoose = require('mongoose');\n\nconst discussionSchema = mongoose.Schema({\n  forum_id"
  },
  {
    "path": "backend/entities/forum/api.js",
    "chars": 915,
    "preview": "// forum controllers\nconst getAllForums = require('./controller').getAllForums;\nconst getDiscussions = require('./contro"
  },
  {
    "path": "backend/entities/forum/controller.js",
    "chars": 2040,
    "preview": "const asyncEach = require('async/each');\n\n// models\nconst Forum = require('./model');\nconst Discussion = require('../dis"
  },
  {
    "path": "backend/entities/forum/model.js",
    "chars": 204,
    "preview": "/**\n * forum model\n */\nconst mongoose = require('mongoose');\n\nconst forumSchema = mongoose.Schema({\n  forum_slug: String"
  },
  {
    "path": "backend/entities/opinion/api.js",
    "chars": 870,
    "preview": "// controllers\nconst getAllOpinions = require('./controller').getAllOpinions;\nconst createOpinion = require('./controlle"
  },
  {
    "path": "backend/entities/opinion/controller.js",
    "chars": 1685,
    "preview": "// models\nconst Opinion = require('./model');\n\n/**\n * get all opinion regarding a single discussion\n * @param  {ObjectId"
  },
  {
    "path": "backend/entities/opinion/model.js",
    "chars": 504,
    "preview": "/**\n * opinion model\n */\nconst mongoose = require('mongoose');\n\nconst opinionSchema = mongoose.Schema({\n  forum_id: mong"
  },
  {
    "path": "backend/entities/user/api.js",
    "chars": 1086,
    "preview": "const passport = require('passport');\nconst signIn = require('./controller').signIn;\nconst getFullProfile = require('./c"
  },
  {
    "path": "backend/entities/user/controller.js",
    "chars": 5141,
    "preview": "const _ = require('lodash');\nconst asyncEach = require('async/each');\n\n// controllers\nconst getAllOpinions = require('.."
  },
  {
    "path": "backend/entities/user/model.js",
    "chars": 486,
    "preview": "/**\n * user model\n */\nconst mongoose = require('mongoose');\n\nconst userSchema = mongoose.Schema({\n  name: String,\n  user"
  },
  {
    "path": "backend/express.js",
    "chars": 1527,
    "preview": "/**\n * module dependencies for express configuration\n */\nconst passport = require('passport');\nconst morgan = require('m"
  },
  {
    "path": "backend/mockData/discussions.js",
    "chars": 2798,
    "preview": "\nconst discussions = [\n  {\n    'forum_id': '58c23d2efce8810b6f20b0b3',\n    'discussion_slug': 'a_pinned_discussion_' + O"
  },
  {
    "path": "backend/mockData/forum.js",
    "chars": 296,
    "preview": "const forums = [\n  {\n    'forum_id': 0,\n    'forum_slug': 'general',\n    'forum_name': 'General',\n  },\n  {\n    'forum_id"
  },
  {
    "path": "backend/mockData/opinions.js",
    "chars": 710,
    "preview": "const opinions = [\n  {\n    'discussion_id': '58c641904e457708a7147417',\n    'user_id': '58c242e2fb2e150d2570e02b',\n    '"
  },
  {
    "path": "backend/mockData/users.js",
    "chars": 1070,
    "preview": "const users = [\n  {\n    'user_id': 0,\n    'username': 'testuser1',\n    'email': 'testuser1@reforum.abc',\n    'avatarUrl'"
  },
  {
    "path": "backend/passport.js",
    "chars": 1389,
    "preview": "/**\n * module dependencies for passport configuration\n */\nconst passport = require('passport');\nconst GitHubStrategy = r"
  },
  {
    "path": "backend/routes.js",
    "chars": 1149,
    "preview": "/**\n * module dependencies for routes configuration\n */\nconst path = require('path');\nconst express = require('express')"
  },
  {
    "path": "backend/utilities/tools.js",
    "chars": 1316,
    "preview": "/**\n * Search object properties recursively and\n * perform callback action on each\n * @param  {Object}   obj      [objec"
  },
  {
    "path": "config/credentials.js",
    "chars": 114,
    "preview": "module.exports = {\n  GITHUB_CLIENT_ID: '',\n  GITHUB_CLIENT_SECRET: '',\n  GITHUB_CALLBACK_URL: '',\n  DBURL: '',\n};\n"
  },
  {
    "path": "config/serverConfig.js",
    "chars": 380,
    "preview": "/**\n * module dependencies for server configuration\n */\nconst path = require('path');\nconst databaseUrl = require('./cre"
  },
  {
    "path": "config/webpack.dev.config.js",
    "chars": 1869,
    "preview": "/**\n * module dependencies for webpack dev configuration\n */\nconst path = require('path');\nconst webpack = require('webp"
  },
  {
    "path": "config/webpack.prod.config.js",
    "chars": 2122,
    "preview": "/**\n * module dependencies for webpack production configuration\n */\nconst path = require('path');\nconst webpack = requir"
  },
  {
    "path": "docs/api.md",
    "chars": 33,
    "preview": "# API Docs\n\nWork in progress :-)\n"
  },
  {
    "path": "docs/system_overview.md",
    "chars": 40,
    "preview": "# System Overview\n\nWork in progress :-)\n"
  },
  {
    "path": "frontend/App/Admin.js",
    "chars": 1524,
    "preview": "import React, { Component } from 'react';\nimport { Link, browserHistory } from 'react-router';\nimport { connect } from '"
  },
  {
    "path": "frontend/App/App.js",
    "chars": 2086,
    "preview": "import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { Helmet } from 'react-helmet';\n"
  },
  {
    "path": "frontend/App/actions.js",
    "chars": 1345,
    "preview": "import _ from 'lodash';\nimport {\n  START_FETCHING_FORUMS,\n  STOP_FETCHING_FORUMS,\n  FETCHING_FORUMS_SUCCESS,\n  FETCHING_"
  },
  {
    "path": "frontend/App/api.js",
    "chars": 186,
    "preview": "import axios from 'axios';\n\nexport const fetchForums = (forum_id) => {\n  return axios.get('/api/forum');\n};\n\nexport cons"
  },
  {
    "path": "frontend/App/constants.js",
    "chars": 616,
    "preview": "export const UPDATECURRENTFORUM = 'update_current_forum';\n\nexport const START_FETCHING_FORUMS = 'start_fetching_forums';"
  },
  {
    "path": "frontend/App/index.js",
    "chars": 1370,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport { Router, Route, browserHistory, IndexRoute } from '"
  },
  {
    "path": "frontend/App/reducers.js",
    "chars": 2374,
    "preview": "import {\n  START_FETCHING_FORUMS,\n  STOP_FETCHING_FORUMS,\n  FETCHING_FORUMS_SUCCESS,\n  FETCHING_FORUMS_FAILURE,\n  UPDATE"
  },
  {
    "path": "frontend/App/store.js",
    "chars": 1078,
    "preview": "import { createStore, applyMiddleware, compose } from 'redux';\nimport { combineReducers } from 'redux';\nimport thunk fro"
  },
  {
    "path": "frontend/App/styles.css",
    "chars": 481,
    "preview": "/**\n * Application container stylings\n */\n\n.loadingWrapper {\n  height: 100vh;\n  width: 100vw;\n  display: flex;\n  justify"
  },
  {
    "path": "frontend/Components/Button/index.js",
    "chars": 1203,
    "preview": "import React, { Component } from 'react';\nimport classnames from 'classnames';\nimport styles from './styles';\n\nclass But"
  },
  {
    "path": "frontend/Components/Button/styles.css",
    "chars": 951,
    "preview": "@value borderColor, secondaryFontColor, primaryFontColor from 'SharedStyles/globalStyles.css';\n\n/**\n * Button styles\n */"
  },
  {
    "path": "frontend/Components/Dashboard/Counts/index.js",
    "chars": 575,
    "preview": "import React, { Component } from 'react';\nimport classnames from 'classnames';\nimport styles from './styles';\n\nclass Cou"
  },
  {
    "path": "frontend/Components/Dashboard/Counts/styles.css",
    "chars": 410,
    "preview": "@value primaryFontColor, secondaryFontColor, borderColor from 'SharedStyles/globalStyles.css';\n@value smallBP from 'Shar"
  },
  {
    "path": "frontend/Components/Dashboard/ForumBox/index.js",
    "chars": 4373,
    "preview": "import React, { Component } from 'react';\nimport classnames from 'classnames';\nimport styles from './styles';\n\nimport Bu"
  },
  {
    "path": "frontend/Components/Dashboard/ForumBox/styles.css",
    "chars": 1462,
    "preview": "@value primaryFontColor, secondaryFontColor, borderColor from 'SharedStyles/globalStyles.css';\n@value smallBP from 'Shar"
  },
  {
    "path": "frontend/Components/FeedBox/DiscussionBox/index.js",
    "chars": 2316,
    "preview": "import React, { Component } from 'react';\nimport { Link } from 'react-router';\nimport classnames from 'classnames';\nimpo"
  },
  {
    "path": "frontend/Components/FeedBox/DiscussionBox/styles.css",
    "chars": 1476,
    "preview": "@value primaryFontColor, secondaryFontColor, borderColor, backShade from 'SharedStyles/globalStyles';\n@value smallBP fro"
  },
  {
    "path": "frontend/Components/FeedBox/index.js",
    "chars": 3185,
    "preview": "import React, { Component } from 'react';\nimport classnames from 'classnames';\nimport Moment from 'moment';\nimport style"
  },
  {
    "path": "frontend/Components/FeedBox/styles.css",
    "chars": 1095,
    "preview": "@value borderColor, backShade, secondaryFontColor, primaryFontColor from 'SharedStyles/globalStyles.css';\n\n/**\n * FeedBo"
  },
  {
    "path": "frontend/Components/Footer/index.js",
    "chars": 451,
    "preview": "import React, { Component } from 'react';\nimport classnames from 'classnames';\n\nimport styles from './styles';\nimport ap"
  },
  {
    "path": "frontend/Components/Footer/styles.css",
    "chars": 176,
    "preview": "@value smallBP, mediumBP, largeBP from 'SharedStyles/globalStyles';\n\n/**\n * Footer styles\n */\n.contentArea {\n  margin-to"
  },
  {
    "path": "frontend/Components/Header/Logo/index.js",
    "chars": 1319,
    "preview": "import React, { Component } from 'react';\nimport styles from './styles';\n\nconst Logo = () => {\n  return (\n    <div class"
  },
  {
    "path": "frontend/Components/Header/Logo/styles.css",
    "chars": 213,
    "preview": "/* logo */\n.logoContainer {\n  display: flex;\n  flex-flow: row nowrap;\n  align-items: center;\n}\n\n.logo {\n  width: 40px;\n "
  },
  {
    "path": "frontend/Components/Header/NavigationBar/index.js",
    "chars": 1443,
    "preview": "import React, { Component } from 'react';\nimport { Link, IndexLink } from 'react-router';\nimport classnames from 'classn"
  },
  {
    "path": "frontend/Components/Header/NavigationBar/styles.css",
    "chars": 716,
    "preview": "@value primaryFontColor, secondaryFontColor, borderColor from 'SharedStyles/globalStyles.css';\n@value smallBP from 'Shar"
  },
  {
    "path": "frontend/Components/Header/UserMenu/index.js",
    "chars": 2878,
    "preview": "import React, { Component } from 'react';\nimport { Link } from 'react-router';\nimport classnames from 'classnames';\nimpo"
  },
  {
    "path": "frontend/Components/Header/UserMenu/styles.css",
    "chars": 1650,
    "preview": "@value primaryFontColor, secondaryFontColor, borderColor from 'SharedStyles/globalStyles.css';\n@value smallBP, mediumBP,"
  },
  {
    "path": "frontend/Components/NewDiscussion/PinButton/index.js",
    "chars": 1264,
    "preview": "import React, { Component } from 'react';\nimport classnames from 'classnames';\nimport styles from './styles';\n\nimport Bu"
  },
  {
    "path": "frontend/Components/NewDiscussion/PinButton/styles.css",
    "chars": 354,
    "preview": "@value primaryFontColor, secondaryFontColor, borderColor from 'SharedStyles/globalStyles.css';\n@value smallBP from 'Shar"
  },
  {
    "path": "frontend/Components/NewDiscussion/TagsInput/index.js",
    "chars": 3289,
    "preview": "import React, { Component } from 'react';\nimport classnames from 'classnames';\nimport _ from 'lodash';\nimport styles fro"
  },
  {
    "path": "frontend/Components/NewDiscussion/TagsInput/styles.css",
    "chars": 655,
    "preview": "@value primaryFontColor, secondaryFontColor, borderColor from 'SharedStyles/globalStyles.css';\n@value smallBP from 'Shar"
  },
  {
    "path": "frontend/Components/RichEditor/BlockStyleControls.js",
    "chars": 1596,
    "preview": "import React, { Component } from 'react';\nimport classnames from 'classnames';\nimport styles from './styles.css';\n\nimpor"
  },
  {
    "path": "frontend/Components/RichEditor/InlineStyleControls.js",
    "chars": 1184,
    "preview": "import React, { Component } from 'react';\nimport classnames from 'classnames';\nimport styles from './styles.css';\n\nimpor"
  },
  {
    "path": "frontend/Components/RichEditor/StyleButton.js",
    "chars": 813,
    "preview": "import React, { Component } from 'react';\nimport classnames from 'classnames';\nimport styles from './styles.css';\n\nclass"
  },
  {
    "path": "frontend/Components/RichEditor/index.js",
    "chars": 4887,
    "preview": "import React, { Component } from 'react';\nimport {\n  Editor,\n  EditorState,\n  ContentState,\n  RichUtils,\n  convertToRaw,"
  },
  {
    "path": "frontend/Components/RichEditor/styles.css",
    "chars": 2164,
    "preview": "@value borderColor, secondaryFontColor, primaryFontColor, backShade from 'SharedStyles/globalStyles.css';\n@value smallBP"
  },
  {
    "path": "frontend/Components/SideBar/index.js",
    "chars": 660,
    "preview": "import React, { Component } from 'react';\nimport { Link } from 'react-router';\nimport styles from './styles';\n\nimport Bu"
  },
  {
    "path": "frontend/Components/SideBar/styles.css",
    "chars": 164,
    "preview": "@value smallBP, mediumBP, largeBP from 'SharedStyles/globalStyles';\n\n/**\n * SideBar component styles\n */\n.sidebarContain"
  },
  {
    "path": "frontend/Components/SingleDiscussion/Discussion/index.js",
    "chars": 4498,
    "preview": "import _ from 'lodash';\nimport React, { Component } from 'react';\nimport { Link } from 'react-router';\nimport moment fro"
  },
  {
    "path": "frontend/Components/SingleDiscussion/Discussion/styles.css",
    "chars": 1798,
    "preview": "@value borderColor, secondaryFontColor, primaryFontColor from 'SharedStyles/globalStyles.css';\n@value smallBP from 'Shar"
  },
  {
    "path": "frontend/Components/SingleDiscussion/Opinion/index.js",
    "chars": 2936,
    "preview": "import React, { Component } from 'react';\nimport { Link } from 'react-router';\nimport moment from 'moment';\nimport class"
  },
  {
    "path": "frontend/Components/SingleDiscussion/Opinion/styles.css",
    "chars": 1994,
    "preview": "@value borderColor, secondaryFontColor, primaryFontColor from 'SharedStyles/globalStyles.css';\n@value smallBP from 'Shar"
  },
  {
    "path": "frontend/Components/SingleDiscussion/ReplyBox/index.js",
    "chars": 736,
    "preview": "import React, { Component } from 'react';\nimport styles from './styles.css';\n\nimport RichEditor from 'Components/RichEdi"
  },
  {
    "path": "frontend/Components/SingleDiscussion/ReplyBox/styles.css",
    "chars": 240,
    "preview": "@value primaryFontColor, secondaryFontColor from 'SharedStyles/globalStyles.css';\n\n/**\n * Reply styles\n */\n\n.loadingWrap"
  },
  {
    "path": "frontend/Components/Tag/index.js",
    "chars": 868,
    "preview": "import React, { Component } from 'react';\nimport classnames from 'classnames';\nimport styles from './styles';\n\nimport Bu"
  },
  {
    "path": "frontend/Components/Tag/styles.css",
    "chars": 466,
    "preview": "@value secondaryFontColor, backShade from 'SharedStyles/globalStyles';\n\n/**\n * Tag styles\n */\n\n.tag {\n  display: inline-"
  },
  {
    "path": "frontend/Components/UserProfile/Profile/index.js",
    "chars": 1143,
    "preview": "import React, { Component } from 'react';\nimport classnames from 'classnames';\nimport styles from './styles.css';\n\nclass"
  },
  {
    "path": "frontend/Components/UserProfile/Profile/styles.css",
    "chars": 450,
    "preview": "@value primaryFontColor, secondaryFontColor from 'SharedStyles/globalStyles.css';\n\n.container {\n  display: flex;\n  align"
  },
  {
    "path": "frontend/Containers/AdminHeader/index.js",
    "chars": 1314,
    "preview": "import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { Link } from 'react-router';\nim"
  },
  {
    "path": "frontend/Containers/AdminHeader/styles.css",
    "chars": 225,
    "preview": "@value smallBP from 'SharedStyles/globalStyles.css';\n\n/**\n * Header Styles\n */\n.headerTop {\n  height: 50px;\n  display: f"
  },
  {
    "path": "frontend/Containers/Header/index.js",
    "chars": 1425,
    "preview": "import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport classnames from 'classnames';\n\ni"
  },
  {
    "path": "frontend/Containers/Header/styles.css",
    "chars": 225,
    "preview": "@value smallBP from 'SharedStyles/globalStyles.css';\n\n/**\n * Header Styles\n */\n.headerTop {\n  height: 50px;\n  display: f"
  },
  {
    "path": "frontend/SharedStyles/appLayout.css",
    "chars": 525,
    "preview": "@value largeBP, mediumBP, smallBP from 'SharedStyles/globalStyles';\n\n/**\n * application layout styles\n */\n.constraintWid"
  },
  {
    "path": "frontend/SharedStyles/globalStyles.css",
    "chars": 529,
    "preview": "/**\n * color variables\n */\n@value themeColor: #F1C40F;\n@value primaryFontColor: #000;\n@value secondaryFontColor: #999;\n@"
  },
  {
    "path": "frontend/Views/AdminDashboard/actions.js",
    "chars": 2400,
    "preview": "import {\n  GET_ALL_INFO_START,\n  GET_ALL_INFO_SUCCESS,\n  GET_ALL_INFO_FAILURE,\n\n  CREATE_FORUM,\n  CREATE_FORUM_SUCCESS,\n"
  },
  {
    "path": "frontend/Views/AdminDashboard/api.js",
    "chars": 362,
    "preview": "import axios from 'axios';\n\nexport const getAdminDashboardInfoAPI = () => {\n  return (axios.get('/api/admin/admin_dashbo"
  },
  {
    "path": "frontend/Views/AdminDashboard/constants.js",
    "chars": 506,
    "preview": "export const GET_ALL_INFO_START = 'get_all_info_start';\nexport const GET_ALL_INFO_SUCCESS = 'get_all_info_success';\nexpo"
  },
  {
    "path": "frontend/Views/AdminDashboard/index.js",
    "chars": 2723,
    "preview": "import React, { Component } from 'react';\nimport { Link } from 'react-router';\nimport { connect } from 'react-redux';\nim"
  },
  {
    "path": "frontend/Views/AdminDashboard/reducers.js",
    "chars": 2173,
    "preview": "import {\n  GET_ALL_INFO_START,\n  GET_ALL_INFO_SUCCESS,\n  GET_ALL_INFO_FAILURE,\n\n  CREATE_FORUM,\n  CREATE_FORUM_SUCCESS,\n"
  },
  {
    "path": "frontend/Views/AdminDashboard/styles.css",
    "chars": 535,
    "preview": "@value smallBP from 'SharedStyles/globalStyles';\n\n.container {\n  margin-top: 20px;\n  position: relative;\n}\n\n.countsConta"
  },
  {
    "path": "frontend/Views/ForumFeed/actions.js",
    "chars": 2883,
    "preview": "import _ from 'lodash';\nimport {\n  START_FETCHING_DISCUSSIONS,\n  STOP_FETCHING_DISCUSSIONS,\n  FETCHING_DISCUSSIONS_SUCCE"
  },
  {
    "path": "frontend/Views/ForumFeed/api.js",
    "chars": 328,
    "preview": "import axios from 'axios';\n\n/**\n * feed apis\n */\nexport const fetchDiscussions = (forum_id, sortingMethod) => {\n  return"
  },
  {
    "path": "frontend/Views/ForumFeed/constants.js",
    "chars": 754,
    "preview": "export const START_FETCHING_DISCUSSIONS = 'start_fetching_discussions';\nexport const STOP_FETCHING_DISCUSSIONS = 'stop_f"
  },
  {
    "path": "frontend/Views/ForumFeed/index.js",
    "chars": 4213,
    "preview": "import React, { Component } from 'react';\nimport { Link } from 'react-router';\nimport { connect } from 'react-redux';\nim"
  },
  {
    "path": "frontend/Views/ForumFeed/reducers.js",
    "chars": 2328,
    "preview": "import {\n  START_FETCHING_DISCUSSIONS,\n  STOP_FETCHING_DISCUSSIONS,\n  FETCHING_DISCUSSIONS_SUCCESS,\n  FETCHING_DISCUSSIO"
  },
  {
    "path": "frontend/Views/ForumFeed/styles.css",
    "chars": 349,
    "preview": "@value largeBP from 'SharedStyles/globalStyles';\n\n/**\n * Forum view styles\n */\n.contentArea {\n  display: flex;\n  margin-"
  },
  {
    "path": "frontend/Views/ForumFeed/tests/actions.test.js",
    "chars": 1920,
    "preview": "import configureStore from 'redux-mock-store';\nimport thunk from 'redux-thunk';\nimport nock from 'nock';\nimport axios fr"
  },
  {
    "path": "frontend/Views/NewDiscussion/actions.js",
    "chars": 3645,
    "preview": "import { browserHistory } from 'react-router';\nimport {\n  POSTING_DISCUSSION_START,\n  POSTING_DISCUSSION_END,\n  POSTING_"
  },
  {
    "path": "frontend/Views/NewDiscussion/api.js",
    "chars": 148,
    "preview": "import axios from 'axios';\n\nexport const postDiscussionApi = (discussion) => {\n  return axios.post('/api/discussion/newD"
  },
  {
    "path": "frontend/Views/NewDiscussion/constants.js",
    "chars": 616,
    "preview": "export const POSTING_DISCUSSION_START = 'posting_discussion_start';\nexport const POSTING_DISCUSSION_END = 'posting_discu"
  },
  {
    "path": "frontend/Views/NewDiscussion/index.js",
    "chars": 4736,
    "preview": "import _ from 'lodash';\nimport React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { Helmet"
  },
  {
    "path": "frontend/Views/NewDiscussion/reducers.js",
    "chars": 1669,
    "preview": "import {\n  POSTING_DISCUSSION_START,\n  POSTING_DISCUSSION_SUCCESS,\n  POSTING_DISCUSSION_FAILURE,\n\n  UPDATE_DISCUSSION_TI"
  },
  {
    "path": "frontend/Views/NewDiscussion/styles.css",
    "chars": 870,
    "preview": "@value largeBP, borderColor, secondaryFontColor from 'SharedStyles/globalStyles';\n\n.content {\n  margin-top: 10px;\n  posi"
  },
  {
    "path": "frontend/Views/NotFound/index.js",
    "chars": 208,
    "preview": "import React, { Component } from 'react';\n\nclass NotFound extends Component {\n  render() {\n    return (\n      <h3>Coudn'"
  },
  {
    "path": "frontend/Views/SingleDiscussion/actions.js",
    "chars": 4979,
    "preview": "import {\n  FETCHING_SINGLE_DISC_START,\n  FETCHING_SINGLE_DISC_END,\n  FETCHING_SINGLE_DISC_SUCCESS,\n  FETCHING_SINGLE_DIS"
  },
  {
    "path": "frontend/Views/SingleDiscussion/api.js",
    "chars": 669,
    "preview": "import axios from 'axios';\n\n/**\n * single discussion apis\n */\nexport const fetchSingleDiscussion = (discussion_slug) => "
  },
  {
    "path": "frontend/Views/SingleDiscussion/constants.js",
    "chars": 1191,
    "preview": "export const FETCHING_SINGLE_DISC_START = 'fetching_single_discussion_start';\nexport const FETCHING_SINGLE_DISC_END = 'f"
  },
  {
    "path": "frontend/Views/SingleDiscussion/index.js",
    "chars": 6760,
    "preview": "import _ from 'lodash';\nimport React, { Component } from 'react';\nimport { browserHistory } from 'react-router';\nimport "
  },
  {
    "path": "frontend/Views/SingleDiscussion/reducers.js",
    "chars": 3233,
    "preview": "import {\n  FETCHING_SINGLE_DISC_START,\n  FETCHING_SINGLE_DISC_END,\n  FETCHING_SINGLE_DISC_SUCCESS,\n  FETCHING_SINGLE_DIS"
  },
  {
    "path": "frontend/Views/SingleDiscussion/styles.css",
    "chars": 493,
    "preview": "@value primaryFontColor, secondaryFontColor from 'SharedStyles/globalStyles.css';\n\n.loadingWrapper {\n  padding: 20px 0px"
  },
  {
    "path": "frontend/Views/UserProfile/actions.js",
    "chars": 703,
    "preview": "import {\n  FETCH_USER_PROFILE_START,\n  FETCH_USER_PROFILE_SUCCESS,\n  FETCH_USER_PROFILE_FAILURE,\n} from './constants';\n\n"
  },
  {
    "path": "frontend/Views/UserProfile/api.js",
    "chars": 165,
    "preview": "/**\n * user profile apis\n */\n\nimport axios from 'axios';\n\nexport const fetchUserProfileApi = (userSlug) => {\n  return ax"
  },
  {
    "path": "frontend/Views/UserProfile/constants.js",
    "chars": 212,
    "preview": "export const FETCH_USER_PROFILE_START = 'fetch_user_profile_start';\nexport const FETCH_USER_PROFILE_SUCCESS = 'fetch_use"
  },
  {
    "path": "frontend/Views/UserProfile/index.js",
    "chars": 2351,
    "preview": "import React, { Component } from 'react';\nimport { connect } from 'react-redux';\nimport { Helmet } from 'react-helmet';\n"
  },
  {
    "path": "frontend/Views/UserProfile/reducers.js",
    "chars": 856,
    "preview": "import {\n  FETCH_USER_PROFILE_START,\n  FETCH_USER_PROFILE_SUCCESS,\n  FETCH_USER_PROFILE_FAILURE,\n} from './constants';\n\n"
  },
  {
    "path": "frontend/Views/UserProfile/styles.css",
    "chars": 334,
    "preview": "@value largeBP from 'SharedStyles/globalStyles';\n\n.container {\n  margin-top: 20px;\n  display: flex;\n  @media largeBP { f"
  },
  {
    "path": "package.json",
    "chars": 2687,
    "preview": "{\n  \"name\": \"reforum\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A forum application built with ReactJS, Redux, Express an"
  },
  {
    "path": "public/build/bundle.js",
    "chars": 444709,
    "preview": "!function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return t[r].call(o.exports"
  },
  {
    "path": "public/build/style.css",
    "chars": 23748,
    "preview": ".styles__loadingWrapper___3oy4O{height:100vh;width:100vw;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-bo"
  },
  {
    "path": "public/index.html",
    "chars": 1245,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta cha"
  },
  {
    "path": "server.js",
    "chars": 592,
    "preview": "// modules for server\nconst path = require('path');\nconst express = require('express');\nconst mongoose = require('mongoo"
  }
]

About this extraction

This page contains the full source code of the shoumma/ReForum GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 126 files (634.6 KB), approximately 203.1k tokens, and a symbol index with 1433 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!