From 13f4957edc7d154496c89ae41bdf6bb96368e092 Mon Sep 17 00:00:00 2001 From: East Date: Wed, 15 May 2019 00:10:31 +0900 Subject: [PATCH 1/3] add generic ulog caption to right side bar for empty ulog subtags --- src/client/app/Sidebar/RightSidebar.js | 6 --- src/client/components/Sidebar/FeedSidebar.js | 3 ++ src/client/feed/Page.js | 4 +- src/client/feed/UlogGenericCaption.js | 52 ++++++++++++++++++++ src/client/helpers/ulogCaptions.js | 7 +++ 5 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 src/client/feed/UlogGenericCaption.js create mode 100644 src/client/helpers/ulogCaptions.js diff --git a/src/client/app/Sidebar/RightSidebar.js b/src/client/app/Sidebar/RightSidebar.js index 4efa5c6280..76095b5ff7 100644 --- a/src/client/app/Sidebar/RightSidebar.js +++ b/src/client/app/Sidebar/RightSidebar.js @@ -83,12 +83,6 @@ export default class RightSidebar extends React.Component { } } const isWitnessVoted = checkVote(); - const { category } = match.params; - const displayUlogCaption = - category && - category.match( - /^(ulog-quotes|ulog-howto|ulog-diy|ulog-surpassinggoogle|teardrops|untalented|ulog-ned|ulography|ulog-gratefulvibes|ulog-resolutions|ulog-memes|ulog-blocktrades|ulog-showerthoughts|ulog-snookmademedoit|ulog-utopian|ulog-thejohalfiles|ulogifs|ulog-surfyogi|ulog-bobbylee|ulog-stellabelle|ulog-sweetsssj|ulog-dimimp|ulog-teamsteem|ulog-kusknee|ulog-papapepper|ulog-steemjet)$/, - ); return (
diff --git a/src/client/components/Sidebar/FeedSidebar.js b/src/client/components/Sidebar/FeedSidebar.js index 4315ca81c3..3c15748921 100644 --- a/src/client/components/Sidebar/FeedSidebar.js +++ b/src/client/components/Sidebar/FeedSidebar.js @@ -14,6 +14,7 @@ import CryptoTrendingCharts from './CryptoTrendingCharts'; import ChatBar from '../../components/Sidebar/ChatBar'; import UlogGamesExchanges from '../../components/Sidebar/UlogGamesExchanges'; import UlogCaption from '../../feed/UlogCaption'; +import UlogGenericCaption from '../../feed/UlogGenericCaption'; @connect( state => ({ @@ -52,11 +53,13 @@ class FeedSidebar extends React.Component { tag.match( /^(ulog-quotes|ulog-howto|ulog-diy|ulog-surpassinggoogle|teardrops|untalented|ulog-ned|ulography|ulog-gratefulvibes|ulog-resolutions|ulog-memes|ulog-blocktrades|ulog-showerthoughts|ulog-snookmademedoit|ulog-utopian|ulog-thejohalfiles|ulogifs|ulog-surfyogi|ulog-bobbylee|ulog-stellabelle|ulog-sweetsssj|ulog-dimimp|ulog-teamsteem|ulog-kusknee|ulog-papapepper|ulog-steemjet)$/, ); + const isStartsWithUlog = tag && tag.startsWith('ulog-'); return (
{!_.isEmpty(currentCrypto) && } {displayUlogCaption && } + {(!displayUlogCaption && isStartsWithUlog) && } diff --git a/src/client/feed/Page.js b/src/client/feed/Page.js index 3d0ec1dd10..b8d0f00a19 100644 --- a/src/client/feed/Page.js +++ b/src/client/feed/Page.js @@ -139,8 +139,8 @@ class Page extends React.Component { ) : ( )} - {displayUlogCaption && } - {(!displayUlogCaption && isStartsWithUlog) && } + {(authenticated && displayUlogCaption) && } + {(authenticated && !displayUlogCaption && isStartsWithUlog) && }
diff --git a/src/client/feed/UlogGenericCaption.js b/src/client/feed/UlogGenericCaption.js new file mode 100644 index 0000000000..e342976ad9 --- /dev/null +++ b/src/client/feed/UlogGenericCaption.js @@ -0,0 +1,52 @@ +/* eslint-disable react/no-unescaped-entities */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { Collapse, Button, Icon } from 'antd'; +import { injectIntl, FormattedMessage } from 'react-intl'; +import ReactMarkdown from 'react-markdown'; +import { generic } from '../helpers/ulogCaptions'; + +@injectIntl +class UlogGenericCaption extends React.Component { + static propTypes = { + category: PropTypes.string.isRequired, + }; + + constructor(props) { + super(props); + } + + render() { + const category = this.props.category; + const about = `About #${category}`; + + return ( +
+ + + +
+ +
+
+
+
+ ); + } +} + +export default UlogGenericCaption; diff --git a/src/client/helpers/ulogCaptions.js b/src/client/helpers/ulogCaptions.js new file mode 100644 index 0000000000..4b0ecf7e7d --- /dev/null +++ b/src/client/helpers/ulogCaptions.js @@ -0,0 +1,7 @@ + +export const generic = `This is an empty #ulog-community.   + Would you like to create a community with it?   +   + Click on **Post Now** below!`; + +export default null; \ No newline at end of file From 7bfab5bc18dbd689448cd993696ba110210cb64b Mon Sep 17 00:00:00 2001 From: East Date: Wed, 15 May 2019 06:52:33 +0900 Subject: [PATCH 2/3] add create community page --- .gitignore | 1 + src/client/community/CreateCommunity.js | 76 +++++++++++++++++++++++++ src/client/helpers/community.js | 17 ++++++ src/common/routes.js | 5 ++ 4 files changed, 99 insertions(+) create mode 100644 src/client/community/CreateCommunity.js create mode 100644 src/client/helpers/community.js diff --git a/.gitignore b/.gitignore index b3f98e6d41..f73fb784c5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .save *.css.map *.css +*.DS_Store # Created by .ignore support plugin (hsz.mobi) ### Node template diff --git a/src/client/community/CreateCommunity.js b/src/client/community/CreateCommunity.js new file mode 100644 index 0000000000..6fdcf40681 --- /dev/null +++ b/src/client/community/CreateCommunity.js @@ -0,0 +1,76 @@ +import React from 'react'; +import ReactMarkdown from 'react-markdown'; +import { message, Collapse, Button, List, Input } from 'antd'; +import * as community from '../helpers/community'; + +class CreateCommunity extends React.Component { + constructor(props) { + super(props); + // bind the component's methods so that it can be called within render() using this.displayComingSoon() + this.displayComingSoon = this.displayComingSoon.bind(this); + } + + /* + * Display a coming soon message when user clicks on any "Click Here" button + */ + displayComingSoon = () => { + message.success('Coming soon!', 3); + }; + + render() { + // for the 'About Ulog' style + const customPanelStyle = { + marginBottom: 5, + overflow: 'hidden', + }; + + // style for the different grow sections + const customCardStyle = { + marginBottom: '10px', + marginTop: '10px', + }; + + return ( +
+
+
+

Create A Ulog-Community

+ + +

+ +

+
+
+ + + + + +

Create A Ulog-Community

+
+ +
+ + +
+ +
+
+
+
+
+
+ ); + } +} + +export default CreateCommunity; diff --git a/src/client/helpers/community.js b/src/client/helpers/community.js new file mode 100644 index 0000000000..b11db8dd15 --- /dev/null +++ b/src/client/helpers/community.js @@ -0,0 +1,17 @@ +export const aboutCommunities = ` +"Communities" is a core aspect of the ulogs.org ecosystem. A ulog-community is a community born around the art of ulogging.   +  +A ulog-subtag however, is a simple fusion between #ulog and a hashtag. Ulog-subtags simplifies the art of ulogging while maintaining its essence, by allowing users to ulog about niche-based topics. It also further incites users to be inventive with content-creation, unites existing STEEM communities by drawing "true fans" towards these communities and enhances content creation/curation.   +  +Each ulog-subtag has the potential of emanating a ulog-community. You can take a step towards creating a ulog-community, if you are a "certified ulogger".   +  + +Visit https://ulogs.org/discover to see if you are "certified". To become a "certified ulogger", contact us. + +  +  + +Read more about ulog-subtags & ulog-communities here! +`; + +export const createCommunity = `Ulog-Community Name (Whatever name you chose will be prefixed by "#ulog-")`; \ No newline at end of file diff --git a/src/common/routes.js b/src/common/routes.js index f402ab30e1..c66c56cdc8 100644 --- a/src/common/routes.js +++ b/src/common/routes.js @@ -29,6 +29,7 @@ import Discover from '../client/discover/Discover'; import Search from '../client/search/Search'; import Ulogging from '../client/ulogging/Ulogging'; import Grow from '../client/grow/Grow'; +import CreateCommunity from '../client/community/CreateCommunity'; import Notifications from '../client/notifications/Notifications'; import Error404 from '../client/statics/Error404'; import ExitPage from '../client/statics/ExitPage'; @@ -169,6 +170,10 @@ const routes = [ path: '/grow', component: Grow, }, + { + path: '/create-community', + component: CreateCommunity, + }, { path: '/bropro', component: Bropro, From f5cc1a8e9fe02c7bbd379625a78c858c71db6454 Mon Sep 17 00:00:00 2001 From: East Date: Thu, 16 May 2019 23:57:02 +0900 Subject: [PATCH 3/3] Add community creation validaiton and post creation --- src/client/Wrapper.js | 3 + src/client/community/CreateCommunity.js | 238 +++++++++++++++++++-- src/client/components/Navigation/Topnav.js | 3 + 3 files changed, 223 insertions(+), 21 deletions(-) diff --git a/src/client/Wrapper.js b/src/client/Wrapper.js index ad9426dabe..3afa2fe50d 100644 --- a/src/client/Wrapper.js +++ b/src/client/Wrapper.js @@ -222,6 +222,9 @@ export default class Wrapper extends React.PureComponent { case 'grow': this.props.history.push('/grow'); break; + case 'create-community': + this.props.history.push('/create-community'); + break; case 'news': this.props.history.push('/trending'); break; diff --git a/src/client/community/CreateCommunity.js b/src/client/community/CreateCommunity.js index 6fdcf40681..51538392d9 100644 --- a/src/client/community/CreateCommunity.js +++ b/src/client/community/CreateCommunity.js @@ -1,30 +1,208 @@ import React from 'react'; +import { connect } from 'react-redux'; +import { injectIntl } from 'react-intl'; +import _ from 'lodash'; +import PropTypes from 'prop-types'; +import { message, Collapse, Button, List, Form, Input } from 'antd'; import ReactMarkdown from 'react-markdown'; -import { message, Collapse, Button, List, Input } from 'antd'; +import steemAPI from '../steemAPI'; +import { getAuthenticatedUser, getIsEditorLoading, getUpvoteSetting } from '../reducers'; +import Action from '../components/Button/Action'; +import { notify } from '../app/Notification/notificationActions'; +import { createPost } from '../post/Write/editorActions'; import * as community from '../helpers/community'; +const version = require('../../../package.json').version; + +@injectIntl +@connect( + state => ({ + user: getAuthenticatedUser(state), + postCreationLoading: getIsEditorLoading(state), + upvote: getUpvoteSetting(state), + }), + { + notify, + createPost, + }, +) +@Form.create() class CreateCommunity extends React.Component { + static propTypes = { + postCreationLoading: PropTypes.bool.isRequired, + upvote: PropTypes.bool.isRequired, + user: PropTypes.shape().isRequired, + intl: PropTypes.shape().isRequired, + community: PropTypes.string, + form: PropTypes.shape().isRequired, + notify: PropTypes.func.isRequired, + createPost: PropTypes.func.isRequired, + }; + + static defaultProps = { + community: '', + currentInputValue: '', + visible: false, + }; + + static minAccountLength = 3; + static maxAccountLength = 16; + constructor(props) { super(props); - // bind the component's methods so that it can be called within render() using this.displayComingSoon() - this.displayComingSoon = this.displayComingSoon.bind(this); + // bind the component's methods so that it can be called within render() using this.method() + this.validateCommunity = this.validateCommunity.bind(this); + this.handleCreatePost = this.handleCreatePost.bind(this); } /* - * Display a coming soon message when user clicks on any "Click Here" button + * Validate inputted community name */ - displayComingSoon = () => { - message.success('Coming soon!', 3); + validateCommunity = (rule, value, callback) => { + const { intl } = this.props; + + if (!value) { + callback(); + return; + } + + // prefix the community name with 'ulog-' + const ulogSubTag = 'ulog-' + value; + + // if subtag is less than min length + if (ulogSubTag.length < CreateCommunity.minAccountLength) { + callback([ + new Error( + intl.formatMessage( + { + id: 'subtag_too_short', + defaultMessage: 'Ulog subtag {subtag} is too short.', + }, + { + subtag: ulogSubTag, + }, + ), + ), + ]); + return; + } + + // if subtag is more than max length + if (ulogSubTag.length > CreateCommunity.maxAccountLength) { + callback([ + new Error( + intl.formatMessage( + { + id: 'subtag_too_long', + defaultMessage: 'Ulog subtag {subtag} is too long.', + }, + { + subtag: ulogSubTag, + }, + ), + ), + ]); + return; + } + + // check if subtag already exists by using get_discussions API + steemAPI.sendAsync('get_discussions_by_created', [{ tag: ulogSubTag, limit: 1 }]).then(result => { + // if no posts already exists, return without error + if (result.length === 0) { + callback(); + } else { + callback([ + new Error( + intl.formatMessage( + { + id: 'community_error_found_tag', + defaultMessage: "Ulog community {subtag} already exists. Please try another one.", + }, + { + subtag: ulogSubTag, + }, + ), + ), + ]); + } + }); + }; + + /* + * Helper function to generate post data + */ + getQuickPostData = () => { + const { form } = this.props; + const postBody = ""; + const community = form.getFieldValue('community'); + const ulogSubTag = "ulog-" + community; + const postTitle = `A New Ulog Community - ${ulogSubTag} - Has Been Created!`; + const tags = [ulogSubTag]; + const data = { + body: postBody, + title: postTitle, + reward: '50', + author: this.props.user.name, + parentAuthor: '', + lastUpdated: Date.now(), + upvote: this.props.upvote, + }; + + const metaData = { + community: 'ulogs', + app: `ulogs/${version}`, + format: 'markdown', + tags, + }; + + data.parentPermlink = ulogSubTag; + data.permlink = _.kebabCase(postTitle); + data.jsonMetadata = metaData; + + return data; + }; + + /* + * Post creation handler when "Create Community" is clicked + */ + handleCreatePost = (e) => { + e.preventDefault(); + + const { form } = this.props; + // validate form fields + form.validateFieldsAndScroll((err, values) => { + // If no error, continue to submit community creation (create a post) + if (!err) { + const community = values.community; + const ulogSubTag = "ulog-" + community; + + if (_.isEmpty(ulogSubTag)) { + this.props.notify( + this.props.intl.formatMessage({ + id: 'community_error_empty_name', + defaultMessage: 'Community name cannot be empty.', + }), + 'error', + ); + return; + } + const data = this.getQuickPostData(); + this.props.createPost(data); + } + }); }; render() { - // for the 'About Ulog' style + const { intl, postCreationLoading } = this.props; + const { getFieldDecorator } = this.props.form; + + // Style for the 'About' description const customPanelStyle = { marginBottom: 5, overflow: 'hidden', }; - // style for the different grow sections + // Style for the sections const customCardStyle = { marginBottom: '10px', marginTop: '10px', @@ -37,14 +215,12 @@ class CreateCommunity extends React.Component {

Create A Ulog-Community

-

- -

+
- +
- - +
+ + {getFieldDecorator('community', { + rules: [ + { + required: true, + message: intl.formatMessage({ + id: 'community_error_empty', + defaultMessage: 'Community is required.', + }), + }, + { validator: this.validateCommunity }, + ], + })( + + )} + + + {postCreationLoading ? "Creating Community..." : "Create Now"} + +
- diff --git a/src/client/components/Navigation/Topnav.js b/src/client/components/Navigation/Topnav.js index 5b18fb15f3..640815e062 100644 --- a/src/client/components/Navigation/Topnav.js +++ b/src/client/components/Navigation/Topnav.js @@ -264,6 +264,9 @@ class Topnav extends React.Component { + + +