From 64f4995ff5a2f70c53eae555192c3bc6ca2425c5 Mon Sep 17 00:00:00 2001
From: Valentine Zavgorodnev <i@valzav.com>
Date: Mon, 17 Oct 2016 17:42:39 -0400
Subject: [PATCH] add HelpContent component (#494)

* add HelpContent component

* update shrinkwrap file

* add faq page

* add 'show beginner's guide' link in blog's empty state section

* update copy and comments
---
 app/ResolveRoute.js                          |   3 +
 app/RootRoute.js                             |   4 +
 app/components/App.jsx                       |   5 +
 app/components/cards/PostsList.jsx           |   4 +-
 app/components/elements/Callout.jsx          |   6 +-
 app/components/elements/HelpContent.jsx      | 110 +++++++++++++
 app/components/pages/Faq.jsx                 |  19 +++
 app/components/pages/RecoverAccountStep2.jsx |   2 +-
 app/components/pages/UserProfile.jsx         |   8 +-
 app/help/en/faq.md                           | 160 +++++++++++++++++++
 npm-shrinkwrap.json                          |  35 ++++
 package.json                                 |   2 +
 shared/UniversalRender.jsx                   |   3 -
 webpack/base.config.js                       |   4 +
 14 files changed, 355 insertions(+), 10 deletions(-)
 create mode 100644 app/components/elements/HelpContent.jsx
 create mode 100644 app/components/pages/Faq.jsx
 create mode 100644 app/help/en/faq.md

diff --git a/app/ResolveRoute.js b/app/ResolveRoute.js
index b01b1e4ec..5e589a088 100644
--- a/app/ResolveRoute.js
+++ b/app/ResolveRoute.js
@@ -6,6 +6,9 @@ export default function resolveRoute(path)
     if (path === '/about.html') {
         return {page: 'About'};
     }
+    if (path === '/faq.html') {
+        return {page: 'Faq'};
+    }
     if (path === '/login.html') {
         return {page: 'Login'};
     }
diff --git a/app/RootRoute.js b/app/RootRoute.js
index a98944530..453e47aec 100644
--- a/app/RootRoute.js
+++ b/app/RootRoute.js
@@ -14,6 +14,10 @@ export default {
             //require.ensure([], (require) => {
                 cb(null, [require('app/components/pages/About')]);
             //});
+        } else if (route.page === 'Faq') {
+            //require.ensure([], (require) => {
+            cb(null, [require('app/components/pages/Faq')]);
+            //});
         } else if (route.page === 'Login') {
             //require.ensure([], (require) => {
             cb(null, [require('app/components/pages/Login')]);
diff --git a/app/components/App.jsx b/app/components/App.jsx
index 5f0eb0d6c..50e8573a0 100644
--- a/app/components/App.jsx
+++ b/app/components/App.jsx
@@ -170,6 +170,11 @@ class App extends React.Component {
                             {translate("whitepaper")}
                         </a>
                     </li>
+                    <li>
+                        <a href="/faq.html" onClick={this.navigate}>
+                            FAQ
+                        </a>
+                    </li>
                     <li>
                         <a onClick={() => depositSteem()}>
                             {translate("buy_steem")}
diff --git a/app/components/cards/PostsList.jsx b/app/components/cards/PostsList.jsx
index 70d96ce8e..1f4d0e0e9 100644
--- a/app/components/cards/PostsList.jsx
+++ b/app/components/cards/PostsList.jsx
@@ -23,7 +23,7 @@ class PostsList extends React.Component {
         loading: PropTypes.bool.isRequired,
         category: PropTypes.string,
         loadMore: PropTypes.func,
-        emptyText: PropTypes.string,
+        emptyText: PropTypes.object,
         showSpam: PropTypes.bool,
         fetchState: PropTypes.func.isRequired,
         pathname: PropTypes.string,
@@ -157,7 +157,7 @@ class PostsList extends React.Component {
         const {account} = this.props
         const {thumbSize, showPost} = this.state
         if (!loading && !posts.length && emptyText) {
-            return <Callout body={emptyText} type="success" />;
+            return <Callout>{emptyText}</Callout>;
         }
         const renderSummary = items => items.map(({item, ignore, netVoteSign, authorRepLog10}) => <li key={item}>
             <PostSummary account={account} post={item} currentCategory={category} thumbSize={thumbSize}
diff --git a/app/components/elements/Callout.jsx b/app/components/elements/Callout.jsx
index 09bb37959..d171919b1 100644
--- a/app/components/elements/Callout.jsx
+++ b/app/components/elements/Callout.jsx
@@ -1,11 +1,11 @@
 import React from 'react';
 
-export default ({title, body, type = 'alert'}) => {
+export default ({title, children, type}) => {
     return <div className="row">
         <div className="column">
-            <div className={'callout ' + type}>
+            <div className={'callout' + (type ? ` ${type}` : '')}>
                 <h4>{title}</h4>
-                <p>{body}</p>
+                <div>{children}</div>
             </div>
         </div>
     </div>
diff --git a/app/components/elements/HelpContent.jsx b/app/components/elements/HelpContent.jsx
new file mode 100644
index 000000000..0c81b50a1
--- /dev/null
+++ b/app/components/elements/HelpContent.jsx
@@ -0,0 +1,110 @@
+import React from "react";
+import MarkdownViewer from 'app/components/cards/MarkdownViewer';
+
+if (!process.env.BROWSER) {
+    // please note we don't need to define require.context for client side rendering because it's defined by webpack
+    const path = require('path');
+    const fs = require('fs');
+    function getFolderContents(folder, recursive) {
+        return fs.readdirSync(folder).reduce(function (list, file) {
+            var name = path.resolve(folder, file);
+            var isDir = fs.statSync(name).isDirectory();
+            return list.concat((isDir && recursive) ? getFolderContents(name, recursive) : [name]);
+        }, []);
+    }
+    function requireContext(folder, recursive, pattern) {
+        var normalizedFolder = path.resolve(path.dirname(module.filename), folder);
+        var folderContents = getFolderContents(normalizedFolder, recursive)
+            .filter(function (item) {
+                if (item === module.filename) return false;
+                return pattern.test(item);
+            });
+
+        var keys = function () {
+            return folderContents;
+        };
+        var returnContext = function returnContext(item) {
+            return fs.readFileSync(item, 'utf8');//require(item);
+        };
+        returnContext.keys = keys;
+        return returnContext;
+    }
+    require.context = requireContext;
+}
+
+let req = require.context("../../help", true, /\.md/);
+let HelpData = {};
+
+function split_into_sections(str) {
+    let sections = str.split(/\[#\s?(.+?)\s?\]/);
+    if (sections.length === 1) return sections[0];
+    if (sections[0].length < 4) sections.splice(0, 1);
+    sections = sections.reduce((result, n) => {
+        let last = result.length > 0 ? result[result.length-1] : null;
+        if (!last || last.length === 2) { last = [n]; result.push(last); }
+        else last.push(n);
+        return result;
+    }, []);
+    return sections.reduce((result, n) => {
+        result[n[0]] = n[1];
+        return result;
+    }, {});
+}
+
+export default class HelpContent extends React.Component {
+
+    static propTypes = {
+        path: React.PropTypes.string.isRequired,
+        section: React.PropTypes.string
+    };
+
+    constructor(props) {
+        super(props);
+        this.locale = 'en';
+    }
+
+    componentWillMount() {
+        const md_file_path_regexp = new RegExp(`\/${this.locale}\/(.+)\.md$`)
+        req.keys().filter(a => {
+            return a.indexOf(`/${this.locale}/`) !== -1;
+        }).forEach(filename => {
+            var res = filename.match(md_file_path_regexp);
+            let key = res[1];
+            let help_locale = HelpData[this.locale];
+            if (!help_locale) HelpData[this.locale] = help_locale = {};
+            let content = req(filename);
+            help_locale[key] = split_into_sections(content);
+        });
+    }
+
+    setVars(str) {
+        return str.replace(/(\{.+?\})/gi, (match, text) => {
+            let key = text.substr(1, text.length - 2);
+            let value = this.props[key] !== undefined ? this.props[key] : text;
+            return value;
+        });
+    }
+
+    render() {
+        if (!HelpData[this.locale]) {
+            console.error(`missing locale '${this.locale}' help files`);
+            return null;
+        }
+        let value = HelpData[this.locale][this.props.path];
+        if (!value && this.locale !== "en") {
+            console.warn(`missing path '${this.props.path}' for locale '${this.locale}' help files, rolling back to 'en'`);
+            value = HelpData['en'][this.props.path];
+        }
+        if (!value) {
+            console.error(`help file not found '${this.props.path}' for locale '${this.locale}'`);
+            return null;
+        }
+        if (this.props.section) value = value[this.props.section];
+        if (!value) {
+            console.error(`help section not found ${this.props.path}#${this.props.section}`);
+            return null;
+        }
+        value = this.setVars(value);
+        return <MarkdownViewer className="HelpContent" text={value} />;
+    }
+}
diff --git a/app/components/pages/Faq.jsx b/app/components/pages/Faq.jsx
new file mode 100644
index 000000000..f7a3d1b52
--- /dev/null
+++ b/app/components/pages/Faq.jsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import HelpContent from 'app/components/elements/HelpContent';
+
+class Faq extends React.Component {
+    render() {
+        return (
+            <div className="row">
+                <div className="column large-8 medium-10 small-12">
+                    <HelpContent path="faq"/>
+                </div>
+            </div>
+        );
+    }
+}
+
+module.exports = {
+    path: 'faq.html',
+    component: Faq
+};
diff --git a/app/components/pages/RecoverAccountStep2.jsx b/app/components/pages/RecoverAccountStep2.jsx
index ac629ac65..ffd5cb565 100644
--- a/app/components/pages/RecoverAccountStep2.jsx
+++ b/app/components/pages/RecoverAccountStep2.jsx
@@ -121,7 +121,7 @@ class RecoverAccountStep2 extends React.Component {
         }
         const {account_to_recover} = this.props;
         if (!account_to_recover) {
-            return <Callout body="Account recovery request is not confirmed yes, please get back later, thank you for your patience." />;
+            return <Callout body="Account recovery request is not confirmed yes, please try back later, thank you for your patience." type="alert" />;
         }
         const {oldPassword, valid, error, progress_status, name_error, success} = this.state;
         const submit_btn_class = 'button action' + (!valid || !oldPassword ? ' disabled' : '');
diff --git a/app/components/pages/UserProfile.jsx b/app/components/pages/UserProfile.jsx
index 9b71b6acd..06a9d7e47 100644
--- a/app/components/pages/UserProfile.jsx
+++ b/app/components/pages/UserProfile.jsx
@@ -166,8 +166,14 @@ export default class UserProfile extends React.Component {
            }
         } else if(!section || section === 'blog') {
             if (account.blog) {
+                const emptyText = isMyAccount ? <div>
+                    Looks like you haven't posted anything yet.<br />
+                    <Link to="/submit.html">Submit a Story</Link><br />
+                    <Link to="/steemit/@thecryptofiend/the-missing-faq-a-beginners-guide-to-using-steemit">Read The Beginner's Guide</Link>
+                </div>:
+                    <div>Looks like {account.name} hasn't started blogging yet!</div>;
                 tab_content = <PostsList
-                    emptyText={`Looks like ${account.name} hasn't started blogging yet!`}
+                    emptyText={emptyText}
                     account={account.name}
                     posts={account.blog}
                     loading={fetching}
diff --git a/app/help/en/faq.md b/app/help/en/faq.md
new file mode 100644
index 000000000..92b09350e
--- /dev/null
+++ b/app/help/en/faq.md
@@ -0,0 +1,160 @@
+# Steemit FAQ 
+
+## What is Steemit.com?
+
+**Steemit** is a social network that empowers people for their contributions by rewarding them for their time, effort and creativity through digital points called **Steem**. 
+
+Steemit has redefined social media by building a living, breathing, growing social economy; a “small town” community where users are getting rewarded for their voice, regardless of race, religion, gender or bias.  
+
+## What is the Steem blockchain?
+
+Steemit is powered by the Steem blockchain, an open source and publicly accessible database, that records all posts and votes, and distributes rewards across the network. 
+
+## What can users upload to Steemit?
+
+Steemit users can upload anything they want, whether it be phrases, quotes, anecdotes, photos, videos, memes, songs; anything that adds value to the ecosystem and generates commentary or critique.
+
+## How does Steemit differ from other social media sites?
+
+Steemit differs entirely from social media giants like Facebook, Reddit and Twitter because it is public and decentralized. Entrepreneurs are beginning to build side apps off the Steem blockchain and we encourage it; through building our thriving community and enhancing the foundation of others, we are changing the way people use and think about the internet. 
+
+## Who are the Steemit co-founders?
+
+Ned Scott, CEO of Steemit, @ned
+https://www.linkedin.com/in/nedscott
+
+Daniel Larimer, CTO of Steemit, @dan @dantheman
+https://www.linkedin.com/in/daniel-larimer-0a367089
+
+## What is the difference between Steem and Steemit?
+
+Steem is the name of both the database and digital points, which are a form of cryptocurrency that Steemit.com plugs into. 
+
+Steemit, Steemit.com and Steemit, Inc. are all names for a privately-owned company and website offering people a secure way to interact with the community network. Steemit is simply an interface to view the blockchain content of Steem.
+
+## Can I earn digital points on Steemit? How?
+
+You can earn digital points on Steemit by:
+
+**Blogging/Posting** - By sharing your original and unique posts, you can get upvotes by community members. Depending on the upvotes you receive, you will get a portion of that day’s total payout.
+
+
+**Curating/Voting** - If you discover a post and upvote it before it becomes popular, you will receive a curation reward. The reward amount will depend on the amount you have vested, called Steem Power.
+
+## Is Steemit a scam?
+
+Steemit is not a scam. A scam begins with you having to give money to someone else. Steemit is not a middleman to any of the digital points allocations users receive. Steemit doesn't ask you to invest anything.  The Steem blockchain and points system is entirely its own entity, similar to Bitcoin, and it should be treated with a high level of due diligence for anyone looking to leverage its digital points as cryptocurrency.
+
+The Steemit team is extremely proud of what has been accomplished building on top of Steem and they are excited about what they’re continuing to build. Any insinuation that they’re doing something improper is abjectly false. Steemit’s blockchain-based transparency is essentially unprecedented in the history of social media networks, including those valued at tens of billions of dollars. 
+
+Steem is based on years of research and development into blockchain technology and built by a team with more than 15+ years combined experience working on blockchain protocols and dapps. Steemit is deeply committed to delivering continuous improvement to the platform now and well into the future. 
+
+Given the long term vision and the further growth it expects, Steemit was designed to leverage a blockchain that has deliberate mechanisms in place to prevent unexpected abandonment of the digital points. The blockchain’s built-in processes on its public, transparent ledger ensure that authors are rewarded for their contribution in Steem Power, Steem and Steem Dollars. 
+
+The founders of Steemit currently own about half of the Steem supply. They are publicly committed to reducing their percentage ownership by distributing Steemit’s digital points as means for covering costs of scaling, operations and signing new users up the platform. 
+
+## How does Steemit respond to public criticism?
+
+Steemit is 100% committed to building the Steemit platform and contributing to Steem wherever possible. With new and disruptive technologies, critics are to be expected, but we believe the issue stems from lack of education.  The Steemit founders openly address criticisms in several podcasts. In regards to founding ownership, Dan and Ned have sold a very small % of their holdings and the @steemit account only powers down to prepare for engagements that fund operations, marketing and development.
+
+The Steemit founders respect people’s right to freedom of speech and their own opinions, but they encourage self-education and research on cutting edge concepts in decentralized social media. 
+
+## Where does the value come from?
+
+At its root, Steem is simply a points system similar to other arcade style points systems, however, because this points system is blockchain based, the points are traded on cryptocurrency markets.  The speculation that occurs on these markets makes the value of the points transparent to everyone who uses Steem-based applications, such as Steemit. 
+
+
+Steem uses pre-determined mathematical formulas to determine how many new points it will issue to users each day, and the Steem network continually creates digital points to reward content creators and curators (for voting on blog posts) at this set and specific rate. Some of the newly-created points are transferred to users who add value to Steemit by blogging, commenting, and voting on other people's blog posts. The remainder is distributed to holders of Steem Power to protect them from dilution.
+
+
+By analogy, Steem is a game system for content, where the rewards people earn are video game tokens that have real market value and are readily tradable for Bitcoin and USD. It is similar to how someone playing a video game could obtain a rare item by playing the game. If they have the scarce item, then they could potentially sell it on video game item markets. 
+
+## What is the difference between Steem, Steem Power, and Steem Dollars?
+
+**Steem**, **Steem Power** and **Steem Dollars** are the three forms of digital points plugged into the Steem blockchain: 
+
+**Steem** - Steem is the most liquid form of currency in the platform. Steem can be converted into Steem Power, Steem Dollars, or traded. 
+
+**Steem Power** - Steem Power is a measurement of how much influence a user can wield via Steemit. The more Steem Power a user holds, the more they may influence the value of the content on the network. It is important to note when a user decides to “Power Down” Steem Power, they will receive equal distributions of the Steem Power as Steem over 104 weeks. 
+
+**Steem Dollars** - Steem Dollars are a blockchain and market powered token designed to be pegged to $1 USD. Steem Dollars may be turned into STEEM before they can be “Powered Up” into Steem Power. Steem Dollars may also be used to buy things in marketplaces, such as Steemit.com and PeerHub.com.
+
+## What is Powering Down and Powering Up?
+
+**Powering Down** - If you have Steem Power, you can begin to Power Down to obtain Steem. The system will transfer 1/104 of your Steem Power, to Steem each week for two years (104 weeks). 
+
+**Powering Up** - If you wish to gain more influence in the Steem network, you must increase your Steem Power. Powering Up is the process of instantaneously turning your Steem into Steem Power.
+
+
+## How does Steemit, Inc. earn money?
+
+Over time, Steemit will allow advertisers and bloggers to promote content by buying and burning Steem. Steemit can benefit from sales of Steem to advertisers in the cryptocurrency markets or offer advanced services to these advertisers and bloggers.
+
+## Will I get a 1099 from Steemit?
+
+No, you are not being paid by Steemit. The Steem network pays you. It is your responsibility to determine what, if any, taxes apply to the transactions you make, and it is your responsibility to report and remit the correct tax to the appropriate tax authority. By creating an account, you agree that Steemit is not responsible for determining whether taxes apply to your Steem transactions or for collecting, reporting, withholding, or remitting any taxes arising from any Steem transactions.   
+
+## Is Steemit decentralized? What about Steem?
+
+Steem as a blockchain is more decentralized than Steemit.com. Steemit, Inc. as a company, may be subject to laws that Steem (as an impersonal blockchain database distributed all over the world) is not.
+
+## Is Steem open-source and is there an API?
+
+Steem is 100% open-source and is the essential API for all data used by sites such as Steemit.com
+https://steemit.com/steemjs/@fabien/steem-api-now-released
+
+## What is available for developers interested in Steem and Steemit? 
+
+Many software engineers are currently leveraging the open-source code to build their applications on Steem. There are more than sixty so far.
+https://github.com/steemit/steem
+
+## What third-party tools are there for Steemit?
+
+There are a lot of them, and people are constantly developing more.
+http://steemtools.com/
+
+## Are my Steem and Steem Dollar tokens insured in the event of a hack or if someone takes over my account?
+
+No, it is not. If your money is in Steem Power, however, it is impossible for a hacker to take out more than 1/104 per week.
+
+## How do I set my recovery account? 
+
+How does one setup the “trusted individual” who can identify you independently of your key?
+https://steemit.com/blockchain/@dan/steemit-releases-groundbreaking-account-recovery-solution
+
+## How does the recovery process work? What should I do first if I discover that someone hacked my account?
+
+You must have already assigned a trusted individual who can identify you independently of your key. Steemit can identify users by their email, Facebook, and Reddit logins (if you signed up through us). You could also use your mother, wife, employer, or friend, or another third party provider.
+
+When you notice your compromised account, you should contact your account recovery partner (the trusted individual) and ask them to submit a request to change the locks on your account. How is this request sent? They verify you by whatever means they find satisfactory and then submit a proposal to the blockchain to change the locks on your account.
+
+Once you submit the proposal to the blockchain, you will have 24 hours to log in with both your old and new keys (aka passwords). Any key you used within the past 30 days is sufficient. If you login in time, then the keys will be changed, and the hacker will be locked out.
+
+If you don't have a key used in the past 30 days, then your account will be unrecoverable.
+
+### What if your Recovery Partner is Hacked too?
+
+In this case, they would appeal to their account recovery partner. Once they recover their account, then they can work with you to recover your account. It is exponentially unlikely that the hacker can compromise all accounts in a very long chain of recovery partners. Therefore, you and your recovery partner should not use each other as their backup.
+https://steemit.com/blockchain/@dan/steemit-releases-groundbreaking-account-recovery-solution
+
+## What are Steem witnesses?
+
+The community also elected 'witnesses' to act as Steem’s governance body, making decisions on improving the platform and preventing early adopters who may attempt to make unfair financial gain.
+
+The Steem blockchain requires a set of people to create blocks and uses a consensus mechanism called Delegated Proof of Stake, or DPOS, in combination with Proof of Work. The people delegated to create these blocks are called witnesses and miners.
+
+Steemit leverages Steem because the founders of Steemit believe Steem’s decentralized governance model makes Steem an excellent platform for supporting long term success of its social network and digital points.
+
+## How can I vote for witnesses?
+
+Users holding Steem Power can vote for witnesses of their choice by visiting this link. https://steemit.com/~witnesses
+
+## Where can I find more information on Steemit?
+
+### Links to Community Created FAQs
+
+http://steemwiki.com
+https://steemit.com/steemit/@thecryptofiend/the-missing-faq-a-beginners-guide-to-using-steemit
+
+### Link to Whitepaper
+https://steem.io/SteemWhitePaper.pdf
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index d0ab6d766..209907e00 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -294,6 +294,11 @@
       "from": "babel-plugin-check-es2015-constants@>=6.3.13 <7.0.0",
       "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.8.0.tgz"
     },
+    "babel-plugin-react-intl": {
+      "version": "2.2.0",
+      "from": "babel-plugin-react-intl@>=2.2.0 <3.0.0",
+      "resolved": "https://registry.npmjs.org/babel-plugin-react-intl/-/babel-plugin-react-intl-2.2.0.tgz"
+    },
     "babel-plugin-syntax-async-functions": {
       "version": "6.8.0",
       "from": "babel-plugin-syntax-async-functions@>=6.8.0 <7.0.0",
@@ -2765,6 +2770,26 @@
       "from": "interpret@>=1.0.0 <2.0.0",
       "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.1.tgz"
     },
+    "intl-format-cache": {
+      "version": "2.0.5",
+      "from": "intl-format-cache@>=2.0.5 <3.0.0",
+      "resolved": "https://registry.npmjs.org/intl-format-cache/-/intl-format-cache-2.0.5.tgz"
+    },
+    "intl-messageformat": {
+      "version": "1.3.0",
+      "from": "intl-messageformat@>=1.3.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-1.3.0.tgz"
+    },
+    "intl-messageformat-parser": {
+      "version": "1.2.0",
+      "from": "intl-messageformat-parser@>=1.2.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/intl-messageformat-parser/-/intl-messageformat-parser-1.2.0.tgz"
+    },
+    "intl-relativeformat": {
+      "version": "1.3.0",
+      "from": "intl-relativeformat@>=1.3.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/intl-relativeformat/-/intl-relativeformat-1.3.0.tgz"
+    },
     "invariant": {
       "version": "2.2.1",
       "from": "invariant@>=2.2.0 <3.0.0",
@@ -4410,6 +4435,11 @@
       "from": "raw-body@>=2.1.2 <2.2.0",
       "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz"
     },
+    "raw-loader": {
+      "version": "0.5.1",
+      "from": "raw-loader@latest",
+      "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz"
+    },
     "react": {
       "version": "15.3.2",
       "from": "react@15.3.2",
@@ -4481,6 +4511,11 @@
       "from": "react-highcharts@>=8.3.3 <9.0.0",
       "resolved": "https://registry.npmjs.org/react-highcharts/-/react-highcharts-8.4.2.tgz"
     },
+    "react-intl": {
+      "version": "2.1.5",
+      "from": "react-intl@>=2.1.3 <3.0.0",
+      "resolved": "https://registry.npmjs.org/react-intl/-/react-intl-2.1.5.tgz"
+    },
     "react-lazy-cache": {
       "version": "3.0.1",
       "from": "react-lazy-cache@>=3.0.1 <4.0.0",
diff --git a/package.json b/package.json
index 8a94a547d..21c04cacd 100644
--- a/package.json
+++ b/package.json
@@ -81,6 +81,7 @@
     "node-sass": "^3.4.2",
     "pluralize": "^2.0.0",
     "purest": "^2.0.1",
+    "raw-loader": "^0.5.1",
     "react": "^15.3.2",
     "react-addons-pure-render-mixin": "^15.3.2",
     "react-addons-test-utils": "^15.3.2",
@@ -139,6 +140,7 @@
     "eslint-plugin-babel": "^3.1.0",
     "eslint-plugin-react": "^4.2.0",
     "extract-text-webpack-plugin": "^1.0.1",
+    "jsdom": "^9.8.0",
     "koa-webpack-dev-middleware": "^1.1.0",
     "koa-webpack-hot-middleware": "^1.0.3",
     "mocha": "^2.4.5",
diff --git a/shared/UniversalRender.jsx b/shared/UniversalRender.jsx
index 9132d9dc7..ed5515b9f 100644
--- a/shared/UniversalRender.jsx
+++ b/shared/UniversalRender.jsx
@@ -12,12 +12,10 @@ import RootRoute from 'app/RootRoute';
 import ErrorPage from 'server/server-error';
 import {createStore, applyMiddleware, compose} from 'redux';
 import { browserHistory } from 'react-router';
-//import useScroll from 'scroll-behavior/lib/useStandardScroll';
 import useScroll from 'react-router-scroll';
 import createSagaMiddleware from 'redux-saga';
 import { syncHistoryWithStore } from 'react-router-redux';
 import rootReducer from 'app/redux/RootReducer';
-// import DevTools from 'app/redux/DevTools';
 import {fetchDataWatches} from 'app/redux/FetchDataSaga';
 import {marketWatches} from 'app/redux/MarketSaga';
 import {sharedWatches} from 'app/redux/SagaShared';
@@ -44,7 +42,6 @@ let middleware;
 if (process.env.BROWSER && process.env.NODE_ENV === 'development') {
     middleware = compose(
         applyMiddleware(sagaMiddleware)
-        // DevTools.instrument()
     );
 } else {
     middleware = applyMiddleware(sagaMiddleware);
diff --git a/webpack/base.config.js b/webpack/base.config.js
index 710ca2f3c..19d3b7aeb 100644
--- a/webpack/base.config.js
+++ b/webpack/base.config.js
@@ -39,6 +39,10 @@ export default {
             {
                 test: /\.scss$/,
                 loader: ExtractTextPlugin.extract('style', 'css!autoprefixer!sass?outputStyle=expanded')
+            },
+            {
+                test: /\.md/,
+                loader: 'raw'
             }
         ]
     },
-- 
GitLab