diff --git a/app/ResolveRoute.js b/app/ResolveRoute.js index 4a891f4baaf67709f693efab98dfa3b5432c71bc..5e589a08888ec4f3b12e4f389ece9bd586360f7d 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'}; } @@ -53,7 +56,8 @@ export default function resolveRoute(path) return {page: 'PostsIndex', params: ['home', match[1]]}; } match = path.match(/^\/(@[\w\.\d-]+)\/?$/) || - path.match(/^\/(@[\w\.\d-]+)\/(blog|posts|recommended|transfers|curation-rewards|author-rewards|permissions|created|recent-replies|feed|password|followed|followers)\/?$/); + // @user/"posts" is deprecated in favor of "comments" as of oct-2016 (#443) + path.match(/^\/(@[\w\.\d-]+)\/(blog|posts|comments|recommended|transfers|curation-rewards|author-rewards|permissions|created|recent-replies|feed|password|followed|followers)\/?$/); if (match) { return {page: 'UserProfile', params: match.slice(1)}; } diff --git a/app/RootRoute.js b/app/RootRoute.js index a9894453005a5c016817009db0679c3aa8ca1753..453e47aeca966f29f0f0fc5eb80d02806be4322c 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 5f0eb0d6c4c9ad003d3ce5b750e941ec101d93e5..50e8573a0240e2d20f5d32af4f82a768d09c83c9 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/PostFull.jsx b/app/components/cards/PostFull.jsx index 026aba8fe8b4a9f93a2bf46c62b293852ed64681..d28b129478519229429377fc6586fe3ba2c4a443 100644 --- a/app/components/cards/PostFull.jsx +++ b/app/components/cards/PostFull.jsx @@ -227,6 +227,7 @@ export default class PostFull extends React.Component { const readonly = post_content.get('mode') === 'archived' || $STM_Config.read_only_mode const showPromote = username && post_content.get('mode') === "first_payout" && post_content.get('depth') == 0 + const showReplyOption = post_content.get('depth') < 6 const showEditOption = username === author const authorRepLog10 = repLog10(content.author_reputation) @@ -260,7 +261,7 @@ export default class PostFull extends React.Component { </span> {!readonly && <span className="PostFull__reply"> - <a onClick={onShowReply}>Reply</a> + {showReplyOption && <a onClick={onShowReply}>Reply</a>} {' '}{showEditOption && !showEdit && <a onClick={onShowEdit}>Edit</a>} {' '}{showDeleteOption && !showReply && <a onClick={onDeletePost}>Delete</a>} </span>} diff --git a/app/components/cards/PostsList.jsx b/app/components/cards/PostsList.jsx index 70d96ce8e8684766172954695d5a2abcb9c5a64a..1f4d0e0e90b75ec4111f5e0b05066b72e0050fc7 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 09bb379596c22bca8d61baf6cde7401b34bf983f..d171919b1674f0fe42abff2572fd848f04ee83a7 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/DropdownMenu.jsx b/app/components/elements/DropdownMenu.jsx index 32052f13df9f9a054aec2ddd3739209be1d6a3b5..53dd5ff582bc001cc5ddca34c61b067ee1c974c4 100644 --- a/app/components/elements/DropdownMenu.jsx +++ b/app/components/elements/DropdownMenu.jsx @@ -45,17 +45,23 @@ export default class DropdownMenu extends React.Component { browserHistory.push(a.pathname + a.search); }; - render() { - const {el, items, selected, children, className, title, href} = this.props; + getSelectedLabel = (items, selected) => { const selectedEntry = items.find(i => i.value === selected) const selectedLabel = selectedEntry && selectedEntry.label ? selectedEntry.label : selected - const entry = <a key="entry" href={href || '#'} onClick={this.show}> - {children || <span> - {/*selectedEntry && selectedEntry.icon && <Icon name={selectedEntry.icon} />*/}{/*looks bad on the deposit screen*/} - {selectedLabel} - <Icon name="dropdown-arrow" /> - </span>} - </a>; + return selectedLabel + } + + render() { + const {el, items, selected, children, className, title, href} = this.props; + const hasDropdown = items.length > 0 + + let entry = children || <span> + {this.getSelectedLabel(items, selected)} + {hasDropdown && <Icon name="dropdown-arrow" />} + </span> + + if(hasDropdown) entry = <a key="entry" href={href || '#'} onClick={this.show}>{entry}</a> + const menu = <VerticalMenu key="menu" title={title} items={items} hideValue={selected} className="VerticalMenu" />; const cls = 'DropdownMenu' + (this.state.shown ? ' show' : '') + (className ? ` ${className}` : '') return React.createElement(el, {className: cls}, [entry, menu]); diff --git a/app/components/elements/HelpContent.jsx b/app/components/elements/HelpContent.jsx new file mode 100644 index 0000000000000000000000000000000000000000..0c81b50a1b39806aa1c9c6f332e8ce1b8e7b6138 --- /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/elements/ReplyEditor.jsx b/app/components/elements/ReplyEditor.jsx index f4733c3976682c4d3ff419b193dc44652ea6a055..5b3abfbef8f64fa27d5f54c283b8cc6da047410e 100644 --- a/app/components/elements/ReplyEditor.jsx +++ b/app/components/elements/ReplyEditor.jsx @@ -449,7 +449,7 @@ export default formId => reduxForm( if (!linkProps) throw new Error('Unknown type: ' + type) - const formCategories = Set(category ? category.replace(/#/g,"").split(/ +/) : []) + const formCategories = Set(category ? category.trim().replace(/#/g,"").split(/ +/) : []) const rootCategory = originalPost && originalPost.category ? originalPost.category : formCategories.first() const rootTag = /^[-a-z\d]+$/.test(rootCategory) ? rootCategory : null diff --git a/app/components/elements/Voting.jsx b/app/components/elements/Voting.jsx index 86593f49ed8e83839a43337738de51249cc35f02..416ee09879811755db3f408ff25e1e391af615e3 100644 --- a/app/components/elements/Voting.jsx +++ b/app/components/elements/Voting.jsx @@ -161,15 +161,23 @@ class Voting extends React.Component { const up = <Icon name={votingUpActive ? 'empty' : 'chevron-up-circle'} />; const classUp = 'Voting__button Voting__button-up' + (myVote > 0 ? ' Voting__button--upvoted' : '') + (votingUpActive ? ' votingUp' : ''); - const payoutItems = [ - {value: 'Potential Payout $' + formatDecimal(pending_payout).join('')}, - {value: 'Promotion Cost $' + formatDecimal(promoted).join('')} - ]; - if (cashout_time && cashout_time.indexOf('1969') !== 0 && cashout_time.indexOf('1970') !== 0) { + const cashout_active = pending_payout > 0 || (cashout_time && cashout_time.indexOf('1969') !== 0 && cashout_time.indexOf('1970') !== 0) + const payoutItems = []; + + if(cashout_active) { + payoutItems.push({value: 'Potential Payout $' + formatDecimal(pending_payout).join('')}); + } + if(promoted > 0) { + payoutItems.push({value: 'Promotion Cost $' + formatDecimal(promoted).join('')}); + } + if (cashout_active) { payoutItems.push({value: <TimeAgoWrapper date={cashout_time} />}); } - if(max_payout < 1000000) { - payoutItems.push({value: 'Max Payout $' + formatDecimal(max_payout).join('')}) + + if(max_payout == 0) { + payoutItems.push({value: 'Payout Declined'}) + } else if (max_payout < 1000000) { + payoutItems.push({value: 'Max Accepted Payout $' + formatDecimal(max_payout).join('')}) } if(total_author_payout > 0) { payoutItems.push({value: 'Past Payouts $' + formatDecimal(total_author_payout + total_curator_payout).join('')}); @@ -179,7 +187,7 @@ class Voting extends React.Component { const payoutEl = <DropdownMenu el="div" items={payoutItems}> <span style={payout_limit_hit ? {opacity: '0.5'} : {}}> <FormattedAsset amount={payout} asset="$" /> - <Icon name="dropdown-arrow" /> + {payoutItems.length > 0 && <Icon name="dropdown-arrow" />} </span> </DropdownMenu>; @@ -197,7 +205,7 @@ class Voting extends React.Component { if (count > MAX_VOTES_DISPLAY) voters.push({value: <span>… and {(count - MAX_VOTES_DISPLAY)} more</span>}); let voters_list = null; - if (showList) { + if (showList && count > 0) { voters_list = <DropdownMenu selected={pluralize('votes', count, true)} className="Voting__voters_list" items={voters} el="div" />; } diff --git a/app/components/modules/Header.jsx b/app/components/modules/Header.jsx index 450270fdc506de759785584fb6531e815ade65c1..b6366aa330a9d84b09084974cd5ec638e4c9b030 100644 --- a/app/components/modules/Header.jsx +++ b/app/components/modules/Header.jsx @@ -129,7 +129,8 @@ class Header extends React.Component { if(route.params[1] === "recent-replies"){ page_title = `Replies by ${user_name} `; } - if(route.params[1] === "posts"){ + // @user/"posts" is deprecated in favor of "comments" as of oct-2016 (#443) + if(route.params[1] === "posts" || route.params[1] === "comments"){ page_title = `Comments by ${user_name} `; } } else { diff --git a/app/components/modules/TopRightMenu.jsx b/app/components/modules/TopRightMenu.jsx index c2846cc923dbf1ac9a7e140d57a4866139415bc2..c68bf343149b733f38d26192b5ad506597a528c1 100644 --- a/app/components/modules/TopRightMenu.jsx +++ b/app/components/modules/TopRightMenu.jsx @@ -26,13 +26,13 @@ function TopRightMenu({username, showLogin, logout, loggedIn, showSignUp, userpi const replies_link = `/@${username}/recent-replies`; const wallet_link = `/@${username}/transfers`; const account_link = `/@${username}`; - const posts_link = `/@${username}/posts`; + const comments_link = `/@${username}/comments`; const reset_password_link = `/@${username}/password`; if (loggedIn) { // change back to if(username) after bug fix: Clicking on Login does not cause drop-down to close #TEMP! const user_menu = [ {link: feed_link, value: 'Feed'}, {link: account_link, value: 'Blog'}, - {link: posts_link, value: 'Comments'}, + {link: comments_link, value: 'Comments'}, {link: replies_link, value: 'Replies'}, {link: wallet_link, value: 'Wallet'}, {link: reset_password_link, value: 'Change Password'}, diff --git a/app/components/pages/Faq.jsx b/app/components/pages/Faq.jsx new file mode 100644 index 0000000000000000000000000000000000000000..f7a3d1b5221f549449b25092c8c75b3ca5092a3d --- /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 ac629ac656f5f2224e6082d5896069fb2d0815cc..ffd5cb565f1f08dee99dc7f6b3ea3773c436ca95 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 4d48799715dcf8952b1f9b1db2923dfc78c86d87..06a9d7e4707827415b9a33ae0db036cbd45371fe 100644 --- a/app/components/pages/UserProfile.jsx +++ b/app/components/pages/UserProfile.jsx @@ -41,7 +41,7 @@ export default class UserProfile extends React.Component { switch(category) { case 'feed': order = 'by_feed'; break; case 'blog': order = 'by_author'; break; - case 'posts': order = 'by_comments'; break; + case 'comments': order = 'by_comments'; break; case 'recent_replies': order = 'by_replies'; break; default: console.log('unhandled category:', category); } @@ -61,6 +61,9 @@ export default class UserProfile extends React.Component { // const gprops = this.props.global.getIn( ['props'] ).toJS(); if( !section ) section = 'blog'; + // @user/"posts" is deprecated in favor of "comments" as of oct-2016 (#443) + if( section == 'posts' ) section = 'comments'; + // const isMyAccount = current_user ? current_user.get('username') === accountname : false; let account let accountImm = this.props.global.getIn(['accounts', accountname]); @@ -145,14 +148,16 @@ export default class UserProfile extends React.Component { /> } } - else if( section === 'posts' && account.post_history ) { + else if( section === 'comments' && account.post_history ) { + // NOTE: `posts` key will be renamed to `comments` (https://github.com/steemit/steem/issues/507) + // -- see also GlobalReducer.js if( account.posts ) { tab_content = <PostsList emptyText={`Looks like ${account.name} hasn't made any comments yet!`} posts={account.posts} loading={fetching} - category="posts" + category="comments" loadMore={this.loadMore} showSpam />; } @@ -161,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} @@ -204,19 +215,7 @@ export default class UserProfile extends React.Component { } let printLink = null; - let section_title = account.name + ' / ' + section; - if( section === 'blog' ) { - section_title = account.name + "'s blog"; - } else if( section === 'transfers' ) { - section_title = account.name + "'s wallet"; - } else if( section === 'curation-rewards' ) { - section_title = account.name + "'s curation rewards"; - } else if( section === 'author-rewards' ) { - section_title = account.name + "'s author rewards"; - } else if( section === 'password' ) { - section_title = '' - } else if( section === 'permissions' ) { - section_title = account.name + "'s permissions" + if( section === 'permissions' ) { if(isMyAccount && wifShown) { printLink = <div> <a className="float-right noPrint" onClick={onPrint}> @@ -225,10 +224,6 @@ export default class UserProfile extends React.Component { </a> </div> } - } else if( section === 'posts' ) { - section_title = account.name + "'s posts"; - } else if( section === 'recent-replies' ) { - section_title = 'Recent replies to ' + account.name + "'s posts"; } const wallet_tab_active = section === 'transfers' || section === 'password' || section === 'permissions' ? 'active' : ''; // className={wallet_tab_active} @@ -242,7 +237,7 @@ export default class UserProfile extends React.Component { <div className="columns small-10 medium-12 medium-expand"> <ul className="menu" style={{flexWrap: "wrap"}}> <li><Link to={`/@${accountname}`} activeClassName="active">Blog</Link></li> - <li><Link to={`/@${accountname}/posts`} activeClassName="active">Comments</Link></li> + <li><Link to={`/@${accountname}/comments`} activeClassName="active">Comments</Link></li> <li><Link to={`/@${accountname}/recent-replies`} activeClassName="active">Replies</Link></li> {/*<li><Link to={`/@${accountname}/feed`} activeClassName="active">Feed</Link></li>*/} <li> @@ -304,7 +299,6 @@ export default class UserProfile extends React.Component { </div> <div className="row"> <div className="column"> - {/*section_title && <h2 className="UserProfile__section-title">{section_title}</h2>*/} {tab_content} </div> </div> diff --git a/app/help/en/faq.md b/app/help/en/faq.md new file mode 100644 index 0000000000000000000000000000000000000000..92b09350e3de8749a0211b02669bdd7cbaead228 --- /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/app/redux/GlobalReducer.js b/app/redux/GlobalReducer.js index 14539f94fccd22d7a966b3e29b7febb11010d4cd..56657ee4cf4d8f1da23177ef2ccc393cbe58b27f 100644 --- a/app/redux/GlobalReducer.js +++ b/app/redux/GlobalReducer.js @@ -152,8 +152,9 @@ export default createModule({ // console.log('-- RECEIVE_DATA state -->', state.toJS()); let new_state; if (order === 'by_author' || order === 'by_feed' || order === 'by_comments' || order === 'by_replies') { - // category is either "blog", "feed", "posts", or "recent_replies" (respectively) -- and all posts are keyed under current profile - const key = ['accounts', accountname, category] + // category is either "blog", "feed", "comments", or "recent_replies" (respectively) -- and all posts are keyed under current profile + // one exception: "comments" category is keyed as "posts" in get_state (https://github.com/steemit/steem/issues/507) + const key = ['accounts', accountname, category == "comments" ? "posts" : category] new_state = state.updateIn(key, List(), list => { return list.withMutations(posts => { data.forEach(value => { diff --git a/app/redux/TransactionSaga.js b/app/redux/TransactionSaga.js index 5ae48f195815533e002198ccc2600e73a809c46a..d2cacea716b8147b0f8aa517fcab09564fb99a02 100644 --- a/app/redux/TransactionSaga.js +++ b/app/redux/TransactionSaga.js @@ -404,7 +404,7 @@ function* error_vote({operation: {author, permlink}}) { // } function slug(text) { - return getSlug(text, {truncate: 128}) + return getSlug(text.replace(/[<>]/g, ''), {truncate: 128}) //const shorten = txt => { // let t = '' // let words = 0 diff --git a/app/utils/Links.js b/app/utils/Links.js index d9e0f59d6240d1d59a46405cd36261bbbf3cea72..7e041997c76f01063c8abda2c3d9a69b948819fe 100644 --- a/app/utils/Links.js +++ b/app/utils/Links.js @@ -1,12 +1,13 @@ const urlChar = '[^\\s"\'<>\\]\\[\\(\\)]' +const urlCharEnd = urlChar.replace(/\]$/, '.,]') // insert bad chars to end on const imagePath = '(?:(?:\\.(?:tiff?|jpe?g|gif|png|svg|ico)|ipfs/[a-z\\d]{40,}))' -const domainPath = '(?:[-a-zA-Z0-9\\._]+)' -const urlChars = '(?:' + urlChar + '*)' +const domainPath = '(?:[-a-zA-Z0-9\\._]*[-a-zA-Z0-9])' +const urlChars = '(?:' + urlChar + '*' + urlCharEnd + ')?' const urlSet = ({domain = domainPath, path} = {}) => { // urlChars is everything but html or markdown stop chars - return `https?:\/\/${domain}(?::\\d{2,5})?(?:[/\\?#]${urlChars}${path ? path + urlChars : ''})${path ? '' : '?'}` + return `https?:\/\/${domain}(?::\\d{2,5})?(?:[/\\?#]${urlChars}${path ? path : ''})${path ? '' : '?'}` } /** diff --git a/app/utils/Links.test.js b/app/utils/Links.test.js index e752686f3e3d7ebae11ceddd06aad59944e88f7c..f55781e2bab81a4d6ad2bbf3493ee81f50c1fc8a 100644 --- a/app/utils/Links.test.js +++ b/app/utils/Links.test.js @@ -13,6 +13,10 @@ describe('Links', () => { match(linksRe.any(), 'https://example.com\n', 'https://example.com') match(linksRe.any(), ' https://example.com ', 'https://example.com') match(linksRe.any(), 'https://example.com ', 'https://example.com') + match(linksRe.any(), 'https://example.com.', 'https://example.com') + match(linksRe.any(), 'https://example.com/page.', 'https://example.com/page') + match(linksRe.any(), 'https://example.com,', 'https://example.com') + match(linksRe.any(), 'https://example.com/page,', 'https://example.com/page') }) it('multiple matches', () => { const all = linksRe.any('ig') diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index d0ab6d76634ed6ac40d0f874f67f7d397cce0914..209907e00e868de9d54c6cbe38e982d520ee98c3 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 8a94a547d8a54c3631f8ea224a922107d516a0c2..21c04cacd436597b1f4803e0ee336082a54bbba9 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 9132d9dc795d48f2d4ab8956f1dad4cdb5e81621..ed5515b9f352302a571093fea965861b3b6be096 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 710ca2f3c7f104d6e746ece0b1d325ca64d86ac6..19d3b7aebce3c0c0b4017235089c230707c88d18 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' } ] },