From 3cb4c6a0081840d8667904e4981ce50d04895bb8 Mon Sep 17 00:00:00 2001 From: 1aerostorm Date: Sat, 8 Oct 2022 15:52:31 +0000 Subject: [PATCH 01/17] Fix authorization --- app/components/App.jsx | 4 +++- app/redux/UserSaga.js | 6 ------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/components/App.jsx b/app/components/App.jsx index ca78109b6..0dc608132 100644 --- a/app/components/App.jsx +++ b/app/components/App.jsx @@ -427,7 +427,9 @@ export default connect( }; }, dispatch => ({ - loginUser: () => dispatch(user.actions.usernamePasswordLogin()), + loginUser: () => { + dispatch(user.actions.usernamePasswordLogin()) + }, logoutUser: () => dispatch(user.actions.logout()), depositSteem: () => { dispatch( diff --git a/app/redux/UserSaga.js b/app/redux/UserSaga.js index 0dfc99965..77ac84061 100644 --- a/app/redux/UserSaga.js +++ b/app/redux/UserSaga.js @@ -304,12 +304,6 @@ function* usernamePasswordLogin2({payload: {username, password, saveLogin, if (!autopost && saveLogin && !operationType) yield put(user.actions.saveLogin()); - if (authSession() || notifySession()) { // if changing account - notifyApiLogout() - authApiLogout() - serverApiLogout() - } - let alreadyAuthorized = false; try { const res = yield notifyApiLogin(username, localStorage.getItem('X-Auth-Session')); From a63ed7e58b1aa3cbdf3197df753c66ef2a926a8e Mon Sep 17 00:00:00 2001 From: 1aerostorm Date: Tue, 11 Oct 2022 20:00:05 +0000 Subject: [PATCH 02/17] Subscribe comments --- app/ResolveRoute.js | 4 +- app/appUpdater.js | 4 +- app/components/cards/Comment.jsx | 15 +- app/components/cards/PostSummary.jsx | 128 +++++++++++++----- app/components/cards/PostSummary.scss | 26 ++++ app/components/modules/TopRightMenu.jsx | 5 +- app/components/pages/Post.jsx | 170 ++++++++++++++++++++--- app/components/pages/Post.scss | 10 ++ app/components/pages/UserProfile.jsx | 26 ++++ app/locales/en.json | 11 +- app/locales/ru-RU.json | 13 +- app/redux/FetchDataSaga.js | 91 ++++++++++--- app/redux/GlobalReducer.js | 22 +++ app/redux/SagaShared.js | 28 +++- app/utils/ApidexApiClient.js | 7 +- app/utils/NotifyApiClient.js | 173 +++++++++++++++++++++--- app/utils/SearchClient.js | 5 +- package.json | 2 +- server/api/general.js | 3 - server/api/uia_address.js | 91 ------------- shared/fetchWithTimeout.js | 10 -- shared/getUIAAddress.js | 57 -------- yarn.lock | 32 +++-- 23 files changed, 655 insertions(+), 278 deletions(-) delete mode 100644 server/api/uia_address.js delete mode 100644 shared/fetchWithTimeout.js delete mode 100644 shared/getUIAAddress.js diff --git a/app/ResolveRoute.js b/app/ResolveRoute.js index b79ad0ecc..6d3c740d7 100644 --- a/app/ResolveRoute.js +++ b/app/ResolveRoute.js @@ -1,9 +1,9 @@ export const routeRegex = { PostsIndex: /^\/(@[\w\.\d-]+)\/feed\/?$/, UserProfile1: /^\/(@[\w\.\d-]+)\/?$/, - UserProfile2: /^\/(@[\w\.\d-]+)\/(blog|posts|comments|reputation|mentions|created|recent-replies|feed|followed|followers|settings)\/??(?:&?[^=&]*=[^=&]*)*$/, + UserProfile2: /^\/(@[\w\.\d-]+)\/(blog|posts|comments|reputation|mentions|created|recent-replies|discussions|feed|followed|followers|settings)\/??(?:&?[^=&]*=[^=&]*)*$/, UserProfile3: /^\/(@[\w\.\d-]+)\/[\w\.\d-]+/, - UserEndPoints: /^(blog|posts|comments|reputation|mentions|created|recent-replies|feed|followed|followers|settings)$/, + UserEndPoints: /^(blog|posts|comments|reputation|mentions|created|recent-replies|discussions|feed|followed|followers|settings)$/, CategoryFilters: /^\/(hot|responses|donates|forums|trending|promoted|allposts|allcomments|created|active)\/?$/ig, PostNoCategory: /^\/(@[\w\.\d-]+)\/([\w\d-]+)/, Post: /^\/([\w\d\-\/]+)\/(\@[\w\d\.-]+)\/([\w\d-]+)\/?($|\?)/, diff --git a/app/appUpdater.js b/app/appUpdater.js index 7a994b357..a054e8e73 100644 --- a/app/appUpdater.js +++ b/app/appUpdater.js @@ -1,14 +1,14 @@ import semver from 'semver' import tt from 'counterpart' -import fetchWithTimeout from 'shared/fetchWithTimeout' +import { fetchEx } from 'golos-lib-js/lib/utils' export async function checkUpdates() { const url = new URL( '/blogs-' + ($STM_Config.platform === 'linux' ? 'linux' : 'win'), $STM_Config.app_updater.host ).toString() - let res = await fetchWithTimeout(url, 3000) + let res = await fetchEx(url, { timeout: 3000 }) res = await res.text() const doc = document.createElement('html') doc.innerHTML = res diff --git a/app/components/cards/Comment.jsx b/app/components/cards/Comment.jsx index b9f7f146e..0a6596fd7 100644 --- a/app/components/cards/Comment.jsx +++ b/app/components/cards/Comment.jsx @@ -78,6 +78,11 @@ class CommentImpl extends PureComponent { if (content) { this._checkHide(content) + const sub_event = content.get('sub_event') + if (sub_event && !this.state.highlight && !this.wasHighlighted) { + this.setState({ highlight: true }) + this.wasHighlighted = true + } } } @@ -220,6 +225,8 @@ class CommentImpl extends PureComponent { ); } + const { sub_event } = comment + return (
+
{controls}
@@ -356,6 +363,12 @@ class CommentImpl extends PureComponent { } } + onClick = () => { + this.setState({ + highlight: false + }) + } + onShowReply = () => { this.setState({ showReply: !this.state.showReply, showEdit: false }); }; diff --git a/app/components/cards/PostSummary.jsx b/app/components/cards/PostSummary.jsx index 711e1fad1..9b244fe5e 100644 --- a/app/components/cards/PostSummary.jsx +++ b/app/components/cards/PostSummary.jsx @@ -1,29 +1,32 @@ -import React from 'react'; +import React from 'react' import PropTypes from 'prop-types' -import { Link } from 'react-router'; -import TimeAgoWrapper from 'app/components/elements/TimeAgoWrapper'; -import Icon from 'app/components/elements/Icon'; -import { connect } from 'react-redux'; -import transaction from 'app/redux/Transaction'; -import user from 'app/redux/User'; -import Reblog from 'app/components/elements/Reblog'; -import MuteAuthorInNew from 'app/components/elements/MuteAuthorInNew'; -import PinPost from 'app/components/elements/PinPost'; -import Voting from 'app/components/elements/Voting'; -import {immutableAccessor} from 'app/utils/Accessors'; -import extractContent from 'app/utils/ExtractContent'; -import { browserHistory } from 'react-router'; -import VotesAndComments from 'app/components/elements/VotesAndComments'; -import TagList from 'app/components/elements/TagList'; -import {authorNameAndRep} from 'app/utils/ComponentFormatters'; -import {Map} from 'immutable'; -import Author from 'app/components/elements/Author'; -import UserNames from 'app/components/elements/UserNames'; -import tt from 'counterpart'; -import { CHANGE_IMAGE_PROXY_TO_STEEMIT_TIME } from 'app/client_config'; -import { detransliterate } from 'app/utils/ParsersAndFormatters'; -import { proxifyImageUrl } from 'app/utils/ProxifyUrl'; +import { FormattedPlural } from 'react-intl' +import { connect } from 'react-redux' +import { Link, browserHistory } from 'react-router' +import {Map} from 'immutable' +import tt from 'counterpart' + +import { CHANGE_IMAGE_PROXY_TO_STEEMIT_TIME } from 'app/client_config' +import Author from 'app/components/elements/Author' +import Icon from 'app/components/elements/Icon' +import MuteAuthorInNew from 'app/components/elements/MuteAuthorInNew' +import PinPost from 'app/components/elements/PinPost' import PostSummaryThumb from 'app/components/elements/PostSummaryThumb' +import Reblog from 'app/components/elements/Reblog' +import TagList from 'app/components/elements/TagList' +import TimeAgoWrapper from 'app/components/elements/TimeAgoWrapper' +import UserNames from 'app/components/elements/UserNames' +import VotesAndComments from 'app/components/elements/VotesAndComments' +import Voting from 'app/components/elements/Voting' +import g from 'app/redux/GlobalReducer' +import transaction from 'app/redux/Transaction' +import user from 'app/redux/User' +import {immutableAccessor} from 'app/utils/Accessors' +import extractContent from 'app/utils/ExtractContent' +import {authorNameAndRep} from 'app/utils/ComponentFormatters' +import { addHighlight, unsubscribePost } from 'app/utils/NotifyApiClient' +import { detransliterate } from 'app/utils/ParsersAndFormatters' +import { proxifyImageUrl } from 'app/utils/ProxifyUrl' import { walletUrl } from 'app/utils/walletUtils' function isLeftClickEvent(event) { @@ -38,7 +41,7 @@ function navigate(e, onClick, post, url, isForum, isSearch) { if (isForum || isSearch) return; if (isModifiedEvent(e) || !isLeftClickEvent(e)) return; - e.preventDefault(); + e.preventDefault() if (onClick) onClick(post, url); else browserHistory.push(url); } @@ -48,6 +51,7 @@ class PostSummary extends React.Component { post: PropTypes.string.isRequired, pending_payout: PropTypes.string.isRequired, total_payout: PropTypes.string.isRequired, + event_count: PropTypes.number, content: PropTypes.object.isRequired, currentCategory: PropTypes.string, thumbSize: PropTypes.string, @@ -67,6 +71,7 @@ class PostSummary extends React.Component { return props.thumbSize !== this.props.thumbSize || props.pending_payout !== this.props.pending_payout || props.total_payout !== this.props.total_payout || + props.event_count !== this.props.event_count || props.username !== this.props.username || props.nsfwPref !== this.props.nsfwPref || state.revealNsfw !== this.state.revealNsfw || @@ -88,6 +93,26 @@ class PostSummary extends React.Component { }) } + unsubscribe = async (e) => { + e.preventDefault() + + const { username, content } = this.props + + if (!username) return + + const author = content.get('author') + const permlink = content.get('permlink') + + try { + await unsubscribePost(username, author, permlink) + } catch (err) { + alert(err.message || err) + return + } + + this.props.unsubscribe(username, author, permlink) + } + render() { const {currentCategory, thumbSize, ignore, onClick} = this.props; const {post, content, pending_payout, total_payout, cashout_time, blockEye} = this.props; @@ -193,6 +218,13 @@ class PostSummary extends React.Component { const link_target = (is_forum || from_search) ? '_blank' : undefined; + let highlight + if (currentCategory === 'discussions') { + highlight = { author: content.get('author'), permlink: content.get('permlink') } + title_link_url = addHighlight(title_link_url) + comments_link = addHighlight(comments_link) + } + let content_body =
navigate(e, onClick, post, title_link_url, is_forum, from_search)}>{desc}
; @@ -207,8 +239,10 @@ class PostSummary extends React.Component { let total_search = content.get('total_search') - let content_title =

- navigate(e, onClick, post, title_link_url, is_forum, from_search)}> + const visitedClassName = this.props.visited ? 'PostSummary__post-visited ' : '' + + let content_title = + navigate(e, onClick, post, title_link_url, is_forum, from_search)}> {title_text} {isOnlyblog && {tt('g.for_followers')}} @@ -216,7 +250,7 @@ class PostSummary extends React.Component { {warn && {detransliterate(nsfwTitle)}} {worker_post && {tt('workers.worker_post')}} {promoted_post && {tt('g.promoted_title')}} -

; + ; // author and category let author_category = @@ -248,7 +282,6 @@ class PostSummary extends React.Component { } } - const visitedClassName = this.props.visited ? 'PostSummary__post-visited ' : ''; let thumb = null; if(pictures && p.image_link) { const size = (thumbSize == 'mobile') ? '800x600' : '256x512' @@ -281,6 +314,34 @@ class PostSummary extends React.Component { return total_search } + let newReplies = null + let unsubscribe = null + if (currentCategory === 'discussions') { + const eventCount = content.get('event_count') + if (eventCount) { + const commFew = tt('comment_jsx.N_comments_2', { N: eventCount }) + const commMany = tt('comment_jsx.N_comments', { N: eventCount }) + newReplies = navigate(e, onClick, post, title_link_url, is_forum, from_search)}> + {'+'} + + + } + unsubscribe = + + + + + } + + content_title =

+ {content_title}{newReplies}{unsubscribe} +

+ return (
{total_search} @@ -295,7 +356,7 @@ class PostSummary extends React.Component { {thumb}
-
+
{content_title}
{content_body} @@ -312,16 +373,18 @@ export default connect( const content = state.global.get('content').get(post); let pending_payout = 0; let total_payout = 0; + let event_count = 0 let gray, ignore if (content) { pending_payout = content.get('pending_payout_value'); total_payout = content.get('total_payout_value'); + event_count = content.get('event_count') const stats = content.get('stats', Map()).toJS() gray = stats.gray ignore = stats.ignore } return { - post, content, gray, ignore, pending_payout, total_payout, + post, content, gray, ignore, pending_payout, total_payout, event_count, username: state.user.getIn(['current', 'username']) || state.offchain.get('account') }; }, @@ -329,6 +392,9 @@ export default connect( (dispatch) => ({ dispatchSubmit: data => { dispatch(user.actions.usernamePasswordLogin({...data})) }, clearError: () => { dispatch(user.actions.loginError({error: null})) }, + unsubscribe: (account, author, permlink) => { + dispatch(g.actions.unsubscribePost({ account, author, permlink })) + }, deleteReblog: (account, author, permlink, successCallback, errorCallback) => { const json = ['delete_reblog', {account, author, permlink}] dispatch(transaction.actions.broadcastOperation({ diff --git a/app/components/cards/PostSummary.scss b/app/components/cards/PostSummary.scss index 8ae51e31e..2a580dc98 100644 --- a/app/components/cards/PostSummary.scss +++ b/app/components/cards/PostSummary.scss @@ -60,6 +60,14 @@ ul.PostsList__summaries { } } +.PostSummary__replies { + font-weight: bold; + color: red; + padding-left: 5px; + font-size: 80%; + text-decoration: none; +} + .PostSummary__image { float: left; width: 130px; @@ -171,6 +179,24 @@ ul.PostsList__summaries { } } } + .unsubscribe { + > a { + color: $medium-gray; + } + > a:visited { + color: $medium-gray; + } + padding-left: 0.5rem; + font-size: 90%; + span { + margin-left: 0.5rem; + } + &:hover { + path { + fill: red; + } + } + } } .PostSummary__collapse { visibility: hidden; diff --git a/app/components/modules/TopRightMenu.jsx b/app/components/modules/TopRightMenu.jsx index 21387ad46..4d67b7142 100644 --- a/app/components/modules/TopRightMenu.jsx +++ b/app/components/modules/TopRightMenu.jsx @@ -79,6 +79,7 @@ function TopRightMenu({account, savings_withdraws, price_per_golos, globalprops, ; const feedLink = `/@${username}/feed`; const repliesLink = `/@${username}/recent-replies`; + const discussionsLink = `/@${username}/discussions` const walletLink = walletUrl(`/@${username}/transfers`) const settingsLink = `/@${username}/settings`; const accountLink = `/@${username}`; @@ -154,13 +155,13 @@ function TopRightMenu({account, savings_withdraws, price_per_golos, globalprops, {link: feedLink, icon: 'new/home', value: tt('g.feed'), addon: }, {link: accountLink, icon: 'new/blogging', value: tt('g.blog')}, {link: repliesLink, icon: 'new/answer', value: tt('g.replies'), addon: }, + {link: discussionsLink, icon: 'new/bell', value: tt('g.discussions'), addon: }, (messagesLink ? {link: messagesLink, icon: 'new/envelope', value: tt('g.messages'), target: '_blank', addon: } : null), {link: mentionsLink, icon: 'new/mention', value: tt('g.mentions'), addon: }, {link: donatesLink, target: walletTarget(), icon: 'editor/coin', value: tt('g.rewards'), addon: }, - {link: walletLink, target: walletTarget(), icon: 'new/wallet', value: tt('g.wallet'), addon: }, - {link: ordersLink, target: walletTarget(), icon: 'trade', value: tt('navigation.market2'), addon: }, + {link: walletLink, target: walletTarget(), icon: 'new/wallet', value: tt('g.wallet'), addon: }, {link: settingsLink, icon: 'new/setting', value: tt('g.settings')}, loggedIn ? {link: '#', icon: 'new/logout', onClick: goChangeAccount, value: tt('g.change_acc')} : diff --git a/app/components/pages/Post.jsx b/app/components/pages/Post.jsx index 658c8cfbc..88b7e7cad 100644 --- a/app/components/pages/Post.jsx +++ b/app/components/pages/Post.jsx @@ -1,19 +1,24 @@ import React from 'react'; import PropTypes from 'prop-types' -import Comment from 'app/components/cards/Comment'; -import PostFull from 'app/components/cards/PostFull'; -import {connect} from 'react-redux'; -import {sortComments} from 'app/utils/comments'; +import {connect} from 'react-redux' +import {Set} from 'immutable' +import tt from 'counterpart' + +import Comment from 'app/components/cards/Comment' +import PostFull from 'app/components/cards/PostFull' import Follow from 'app/components/elements/Follow' +import FoundationDropdownMenu from 'app/components/elements/FoundationDropdownMenu' +import Icon from 'app/components/elements/Icon' +import IllegalContentMessage from 'app/components/elements/IllegalContentMessage' import LoadingIndicator from 'app/components/elements/LoadingIndicator'; -import FoundationDropdownMenu from 'app/components/elements/FoundationDropdownMenu'; -import IllegalContentMessage from 'app/components/elements/IllegalContentMessage'; -import {Set} from 'immutable' -import tt from 'counterpart'; -import shouldComponentUpdate from 'app/utils/shouldComponentUpdate'; -import { authRegisterUrl, } from 'app/utils/AuthApiClient' +import g from 'app/redux/GlobalReducer' import user from 'app/redux/User' +import { authRegisterUrl, } from 'app/utils/AuthApiClient' +import { sortComments } from 'app/utils/comments' +import { subscribePost, unsubscribePost, getSubs } from 'app/utils/NotifyApiClient' +import shouldComponentUpdate from 'app/utils/shouldComponentUpdate' import session from 'app/utils/session' +import { isHighlight, markCommentsRead } from 'app/utils/NotifyApiClient' class Post extends React.Component { static propTypes = { @@ -31,12 +36,91 @@ class Post extends React.Component { showNegativeComments: false }; this.shouldComponentUpdate = shouldComponentUpdate(this, 'Post') + this.commentsRef = React.createRef() + } + + async componentDidMount() { + if (process.env.BROWSER) { + const account = session.load().currentName + if (!account) return + + const dis = this.getDiscussion() + if (!dis) return + + const author = dis.get('author') + const permlink = dis.get('permlink') + + let found = false + const res = await getSubs(account) + if (res.result) { + for (const sub of res.result.subs) { + const [ subAuthor, subPermlink ] = sub.entityId.split('|') + if (subAuthor === author && subPermlink === permlink) { + found = true + break + } + } + } + this.setState({ subscribed: found }) + + this.processHighlight() + } + } + + async componentDidUpdate() { + this.processHighlight() } - componentDidMount() { - if (window.location.hash.indexOf('comments') !== -1) { - const comments_el = document.getElementById('comments'); - if (comments_el) comments_el.scrollIntoView(); + processHighlight() { + const curUser = session.load().currentName + if (!curUser) { + return + } + const dis = this.getDiscussion() + if (!dis) { + return null + } + const replies = dis.get('replies').toJS() + const loaded = replies.length || !dis.get('children') + if (loaded) { + const author = dis.get('author') + const permlink = dis.get('permlink') + + const highlight = isHighlight() + if (!this.state.highlight) + this.setState({ highlight }) + + if (highlight) { + if (!dis.get('highlighted')) { + return null + } + markCommentsRead(curUser, author, permlink) + this.props.markSubRead(author, permlink) + this.readen = true + let counter = 0 + const scroller = setInterval(() => { + let notYet = false + for (const img of document.getElementsByTagName('img')) { + if (!img.complete) { + notYet = true + break + } + } + ++counter + if ((notYet && counter < 2000) || !this.commentsRef.current) return + + const proceed = () => { + clearInterval(scroller) + this.commentsRef.current.scrollIntoView() + document.scrollingElement.scrollTop -= 200 + } + if (counter > 100) { + setTimeout(proceed, 500) + } else { + proceed() + } + }, 1) + } } } @@ -104,19 +188,52 @@ class Post extends React.Component { return this._renderStub(children) } + subscribe = async (e, dis) => { + e.preventDefault() + try { + const { current_user, } = this.props + const account = current_user && current_user.get('username') + if (!account) return + if (this.state.subscribed) { + await unsubscribePost(account, dis.get('author'), dis.get('permlink')) + this.setState({subscribed: false}) + return + } + await subscribePost(account, dis.get('author'), dis.get('permlink')) + this.setState({subscribed: true}) + } catch (err) { + alert(err.message || err) + } + } + + getDiscussion = (postGetter = () => {}) => { + let { content, post, routeParams } = this.props + if (!post) { + post = routeParams.username + '/' + routeParams.slug + } + postGetter(post) + const dis = content.get(post) + return dis + } + render() { const {following, content, current_user} = this.props const {showNegativeComments, commentHidden, showAnyway} = this.state let { post } = this.props; const { aiPosts } = this.props; - if (!post) { - const route_params = this.props.routeParams; - post = route_params.username + '/' + route_params.slug; - } - const dis = content.get(post); + const dis = this.getDiscussion(p => post = p) if (!dis) return null; + if (process.env.BROWSER) { + const author = dis.get('author') + const permlink = dis.get('permlink') + const highlight = isHighlight() + if (highlight && !dis.get('highlighted') && !this.readen) { + return this._renderLoadingStub() + } + } + const stats = dis.get('stats').toJS() if(!showAnyway) { @@ -134,7 +251,7 @@ class Post extends React.Component { let replies = dis.get('replies').toJS(); - let sort_order = 'trending'; + let sort_order = this.state.highlight ? 'new' : 'trending'; if( this.props.location && this.props.location.query.sort ) sort_order = this.props.location.query.sort; @@ -224,6 +341,8 @@ class Post extends React.Component { if($STM_Config.blocked_users.includes(post.split("/")[0])) { return () } + + const { subscribed } = this.state return (
@@ -242,12 +361,18 @@ class Post extends React.Component {
-
+
{(!replies.length && dis.get('children')) ? (
) : null} +
this.subscribe(e, dis)}> + + + {subscribed ? tt('post_jsx.unsubscribe') : tt('post_jsx.subscribe_comments')} + +
{positiveComments.length ? (
{tt('post_jsx.sort_order')}:   @@ -300,5 +425,8 @@ export default connect((state, props) => { if (e) e.preventDefault(); dispatch(user.actions.showLogin()) }, + markSubRead: (author, permlink) => { + dispatch(g.actions.markSubRead({ author, permlink })) + } }) )(Post); diff --git a/app/components/pages/Post.scss b/app/components/pages/Post.scss index ac13ce177..daa662caa 100644 --- a/app/components/pages/Post.scss +++ b/app/components/pages/Post.scss @@ -1,3 +1,13 @@ +.Post__comments_subscribe { + margin-top: 0.5rem; + color: $dark-gray; + font-size: 94%; + cursor: pointer; + > span { + margin-left: 5px; + } +} + .Post__comments_sort_order { margin: 0.5rem 0; color: $dark-gray; diff --git a/app/components/pages/UserProfile.jsx b/app/components/pages/UserProfile.jsx index bf3e20709..e208beb8f 100644 --- a/app/components/pages/UserProfile.jsx +++ b/app/components/pages/UserProfile.jsx @@ -346,6 +346,29 @@ export default class UserProfile extends React.Component { tab_content = (
); } } + else if( (section === 'discussions')) { + if (account.discussions) { + let posts = accountImm.get('discussions'); + if (posts && !posts.size) { + tab_content = {tt('user_profile.user_hasnt_followed_anything', {name: accountname}) + '.'} + } else { + tab_content = ( +
+ + {isMyAccount &&
} +
+ ) + } + } else { + tab_content = (
); + } + } else if( (section === 'reputation')) { tab_content = (
@@ -397,6 +420,9 @@ export default class UserProfile extends React.Component {
{tt('g.blog')} {tt('g.comments')} + {isMyAccount ? + {tt('g.discussions')} + : null} {tt('g.replies')} {isMyAccount && } diff --git a/app/locales/en.json b/app/locales/en.json index 8a32a0685..26d86eeb8 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -115,6 +115,7 @@ "date": "Date", "decrypt": "Decrypt", "delete": "Delete", + "discussions": "Discussions", "dismiss": "Dismiss", "documentation": "Documentation", "edit": "Edit", @@ -532,6 +533,7 @@ "user_hasnt_started_bloggin_yet": "Looks like %(name)s hasn't started blogging yet!", "user_hasnt_followed_anything_yet": "Looks like %(name)s might not be following anyone yet! If %(name)s recently added new users to follow, their personalized feed will populate once new content is available.", "user_hasnt_had_any_replies_yet": "%(name)s hasn't had any replies yet", + "user_hasnt_followed_anything": "Subscribe to new comments of a post, and you will immediately receive them on that page.", "if_you_recently_added_new_users_to_follow": "If you recently added new users to follow, your personalized feed will populate once new content is available.", "explore_APP_NAME": "Explore %(APP_NAME)s", "full_faq": "The full F.A.Q on %(APP_NAME)s", @@ -598,7 +600,9 @@ "post_jsx": { "now_showing_comments_with_low_ratings": "Now showing comments with low ratings", "sort_order": "Sort Order", - "comments_were_hidden_due_to_low_ratings": "Comments were hidden due to low ratings" + "comments_were_hidden_due_to_low_ratings": "Comments were hidden due to low ratings", + "subscribe_comments": "Subscribe New", + "subscribe_comments_long": "Subscribe New Comments" }, "do_not_bother": { "title": "Do not bother", @@ -933,7 +937,10 @@ "reveal_comment": "Reveal comment", "show_1_more_reply": "Show 1 more reply", "show_N_more_replies": "Show %(N)s more replies", - "show_N_more_replies_2": "Show %(N)s more replies" + "show_N_more_replies_2": "Show %(N)s more replies", + "1_comment": "1 reply", + "N_comments": "%(N)s replies", + "N_comments_2": "%(N)s replies" }, "poststub": { "onlyblog": "This post is only for author's followers.", diff --git a/app/locales/ru-RU.json b/app/locales/ru-RU.json index 0076cbda6..93ce26a37 100644 --- a/app/locales/ru-RU.json +++ b/app/locales/ru-RU.json @@ -219,6 +219,7 @@ "date": "Дата", "decrypt": "Расшифровать", "delete": "Удалить", + "discussions": "Обсуждения", "dismiss": "Скрыть", "documentation": "Документация", "edit": "Редактировать", @@ -601,7 +602,10 @@ "reveal_comment": "Показать комментарий", "show_1_more_reply": "Еще 1 комментарий", "show_N_more_replies": "Еще %(N)s комментариев", - "show_N_more_replies_2": "Еще %(N)s комментария" + "show_N_more_replies_2": "Еще %(N)s комментария", + "1_comment": "1 комментарий", + "N_comments": "%(N)s комментариев", + "N_comments_2": "%(N)s комментария" }, "market_jsx": { "action": "Действие", @@ -693,7 +697,11 @@ "post_jsx": { "now_showing_comments_with_low_ratings": "Отображаем скрытые комментарии", "sort_order": "Порядок сортировки", - "comments_were_hidden_due_to_low_ratings": "Часть комментариев скрыта из-за низкого рейтинга или автором блога" + "comments_were_hidden_due_to_low_ratings": "Часть комментариев скрыта из-за низкого рейтинга или автором блога", + "subscribe_comments": "Подписаться на новые", + "subscribe_comments_long": "Подписаться на новые комментарии", + "unsubscribe": "Отписаться от новых", + "unsubscribe_long": "Отписаться от новых комментариев" }, "postfull_jsx": { "this_post_is_not_available_due_to_breach_of_legislation": "Этот пост недоступен из-за жалобы о нарушении законодательства.", @@ -997,6 +1005,7 @@ "user_hasnt_started_bloggin_yet": "Похоже, что %(name)s еще не завёл блог!", "user_hasnt_followed_anything_yet": "Похоже, что %(name)s еще ни на кого не подписан! Если, %(name)s недавно подписался на новых пользователей, их персонализированный канал будет заполняться сразу после появления нового контента.", "user_hasnt_had_any_replies_yet": "%(name)s еще не получил ответов", + "user_hasnt_followed_anything": "Подпишитесь на комментарии к любому посту, и вы будете получать уведомления обо всех новых комментариях.", "if_you_recently_added_new_users_to_follow": "Если вы недавно добавили новых пользователей, ваш персонализированный фид будет заполняться сразу после появления нового контента.", "explore_APP_NAME": "Исследовать %(APP_NAME)s", "full_faq": "Детальное FAQ %(APP_NAME)s", diff --git a/app/redux/FetchDataSaga.js b/app/redux/FetchDataSaga.js index 758a8eaca..1df8f5635 100644 --- a/app/redux/FetchDataSaga.js +++ b/app/redux/FetchDataSaga.js @@ -6,11 +6,13 @@ import { getPinnedPosts, getMutedInNew } from 'app/utils/NormalizeProfile'; import {loadFollows, fetchFollowCount} from 'app/redux/FollowSaga'; import { getBlockings, listBlockings } from 'app/redux/BlockingSaga' import { contentPrefs as prefs } from 'app/utils/Allowance' -import {getContent} from 'app/redux/SagaShared'; +import { applyEventHighlight, getContent } from 'app/redux/SagaShared' import GlobalReducer from './GlobalReducer'; import constants from './constants'; +import session from 'app/utils/session' import { reveseTag, getFilterTags } from 'app/utils/tags'; import { PUBLIC_API, CATEGORIES, SELECT_TAGS_KEY, DEBT_TOKEN_SHORT, LIQUID_TICKER } from 'app/client_config'; +import { getSubs, } from 'app/utils/NotifyApiClient' import { SearchRequest, searchData, stateSetVersion } from 'app/utils/SearchClient' export function* fetchDataWatches () { @@ -105,6 +107,7 @@ export function* fetchState(location_change_action) { const uname = parts[0].substr(1) const [ account ] = yield call([api, api.getAccountsAsync], [uname]) state.accounts[uname] = account + delete state.accounts[uname].discussions if (account) { state.accounts[uname].tags_usage = yield call([api, api.getTagsUsedByAuthorAsync], uname) @@ -125,6 +128,40 @@ export function* fetchState(location_change_action) { }) break + case 'discussions': + state.accounts[uname].discussions = [] + const account = session.load().currentName + if (account && uname === account) { + let res, subs + try { + res = yield getSubs(account) + } catch (err) { + } + const ids = [] + if (res && res.result) { + subs = res.result.subs + for (let sub of subs) { + const [ author, permlink ] = sub.entityId.split('|') + ids.push({ + author, + hashlink: sub.hashlink + }) + } + } + if (ids.length) { + const previews = yield call([api, api.getContentPreviewsAsync], ids, 500) + for (const i in previews) { + const { author, permlink } = previews[i] + checkAuthor(author) + const link = `${author}/${permlink}` + state.accounts[uname].discussions.push(link) + state.content[link] = previews[i] + state.content[link].event_count = subs[i] ? subs[i].eventCount : 0 + } + } + } + break + case 'posts': case 'comments': const filter_tags = curUser ? ['test'] : getFilterTags() @@ -138,25 +175,34 @@ export function* fetchState(location_change_action) { }) break - case 'feed': + case 'feed': { const feedEntries = yield call([api, api.getFeedEntriesAsync], uname, 0, 20, ['fm-']) state.accounts[uname].feed = [] - for (let key in feedEntries) { - const { author, permlink } = feedEntries[key] + const ids = [] + + for (const i in feedEntries) { + const { author, hashlink } = feedEntries[i] + + ids.push({ author, hashlink }) + } + + const previews = yield call([api, api.getContentPreviewsAsync], ids, 10000) + for (const i in previews) { + const { author, permlink } = previews[i] const link = `${author}/${permlink}` state.accounts[uname].feed.push(link) - state.content[link] = yield call([api, api.getContentAsync], author, permlink, constants.DEFAULT_VOTE_LIMIT) + state.content[link] = previews[i] checkAuthor(author) - if (feedEntries[key].reblog_by.length > 0) { - state.content[link].first_reblogged_by = feedEntries[key].reblog_by[0] - state.content[link].reblogged_by = feedEntries[key].reblog_by - state.content[link].first_reblogged_on = feedEntries[key].reblog_on + if (feedEntries[i].reblog_by.length > 0) { + state.content[link].first_reblogged_by = feedEntries[i].reblog_by[0] + state.content[link].reblogged_by = feedEntries[i].reblog_by + state.content[link].first_reblogged_on = feedEntries[i].reblog_on } } - break + break } case 'reputation': const rhistory = yield call([api, api.getAccountHistoryAsync], uname, -1, 1000, {select_ops: ['account_reputation']}); @@ -192,15 +238,25 @@ export function* fetchState(location_change_action) { let pinnedPosts = getPinnedPosts(account) blogEntries.unshift(...pinnedPosts) - for (let key in blogEntries) { - const { author, permlink } = blogEntries[key] + const ids = [] + + for (const i in blogEntries) { + const { author, hashlink } = blogEntries[i] + + ids.push({ author, hashlink }) + } + + const previews = yield call([api, api.getContentPreviewsAsync], ids, 10000) + for (const i in previews) { + const { author, permlink } = previews[i] + const link = `${author}/${permlink}` - state.content[link] = yield call([api, api.getContentAsync], author, permlink, constants.DEFAULT_VOTE_LIMIT) + state.content[link] = previews[i] state.accounts[uname].blog.push(link) - - if (blogEntries[key].reblog_on !== '1970-01-01T00:00:00') { - state.content[link].first_reblogged_on = blogEntries[key].reblog_on + + if (blogEntries[i].reblog_on !== '1970-01-01T00:00:00') { + state.content[link].first_reblogged_on = blogEntries[i].reblog_on } } break @@ -260,8 +316,11 @@ export function* fetchState(location_change_action) { state.content[link].donate_uia_list = yield call([api, api.getDonatesAsync], true, {author: reply.author, permlink: reply.permlink}, '', '', 20, 0, true) } state.content[link].confetti_active = false + state.content[link].sub_event = false } + yield applyEventHighlight(state.content, account, permlink, curUser) + let args = { truncate_body: 128, select_categories: [category], filter_tag_masks: ['fm-'], filter_tags: getFilterTags(), prefs: prefs(curUser) }; diff --git a/app/redux/GlobalReducer.js b/app/redux/GlobalReducer.js index 1cc2ea0ae..59c7b9b1c 100644 --- a/app/redux/GlobalReducer.js +++ b/app/redux/GlobalReducer.js @@ -152,6 +152,17 @@ export default createModule({ }); }, }, + { + action: 'MARK_SUB_READ', + reducer: (state, { payload: { author, permlink } }) => { + const key = author + '/' + permlink + return state.updateIn(['content', key], Map(), c => { + c = c.set('highlighted', false) + c = c.set('event_count', 0) + return c + }) + } + }, { action: 'RECEIVE_WORKER_REQUEST', reducer: (state, { payload: { wr } }) => { @@ -439,6 +450,17 @@ export default createModule({ return newState; }, }, + { + action: 'UNSUBSCRIBE_POST', + reducer: (state, { payload: { account, author, permlink } }) => { + const link = `${author}/${permlink}` + const newState = state.updateIn(['accounts', account, 'discussions'], data => { + data = data.filter(v => v !== link) + return data + }) + return newState + } + }, { action: 'REQUEST_META', reducer: (state, { payload: { id, link } }) => diff --git a/app/redux/SagaShared.js b/app/redux/SagaShared.js index a16891f73..18d9be38b 100644 --- a/app/redux/SagaShared.js +++ b/app/redux/SagaShared.js @@ -1,8 +1,10 @@ import { fromJS } from 'immutable' -import { fork, call, put, select, takeEvery } from 'redux-saga/effects'; +import { fork, call, put, select, takeEvery } from 'redux-saga/effects' +import { api } from 'golos-lib-js' + +import constants from './constants' import g from 'app/redux/GlobalReducer' -import constants from './constants'; -import { api } from 'golos-lib-js'; +import { isHighlight, getEvents } from 'app/utils/NotifyApiClient' export function* sharedWatches() { yield fork(watchTransactionErrors) @@ -64,3 +66,23 @@ export function* getWorkerRequest({author, permlink, voter}) { yield put(g.actions.receiveWorkerRequest({wr})) } } + +export function* applyEventHighlight(contentMap, author, permlink, curUser) { + if (isHighlight() && curUser) { + let events = [] + try { + events = yield getEvents(curUser, author, permlink) + } catch (err) {} + const now = Date.now() + for (const event of events) { + try { + const ed = JSON.parse(event.data) + const link = `${ed.author}/${ed.permlink}` + contentMap[link].sub_event = event + } catch (err) { + console.error('Cannot apply Sub API event', event.data, err) + } + } + contentMap[author + '/' + permlink].highlighted = true + } +} diff --git a/app/utils/ApidexApiClient.js b/app/utils/ApidexApiClient.js index 21ba37347..0138b4d45 100644 --- a/app/utils/ApidexApiClient.js +++ b/app/utils/ApidexApiClient.js @@ -1,4 +1,4 @@ -import fetchWithTimeout from 'shared/fetchWithTimeout' +import { fetchEx } from 'golos-lib-js/lib/utils' const request_base = { method: 'get', @@ -44,7 +44,10 @@ export async function apidexGetPrices(sym) { if (cache && (now - cache.time) < 60000) { return cache.resp } else { - let resp = await fetchWithTimeout(apidexUrl(`/api/v1/cmc/${sym}`), 2000, request) + let resp = await fetchEx(apidexUrl(`/api/v1/cmc/${sym}`), { + ...request, + timeout: 2000 + }) resp = await resp.json() if (resp.data && resp.data.slug) resp['page_url'] = getPageURL(resp.data.slug) diff --git a/app/utils/NotifyApiClient.js b/app/utils/NotifyApiClient.js index f8f7c5cbd..c675b6b1b 100644 --- a/app/utils/NotifyApiClient.js +++ b/app/utils/NotifyApiClient.js @@ -1,11 +1,13 @@ -const request_base = { +import { fetchEx } from 'golos-lib-js/lib/utils' + +const requestBase = () => ({ method: 'post', credentials: 'include', headers: { Accept: 'application/json', 'Content-Type': 'application/json' } -}; +}) const notifyAvailable = () => { return process.env.BROWSER && typeof($STM_Config) !== 'undefined' @@ -38,11 +40,11 @@ function saveSession(response) { export function notifyApiLogin(account, authSession) { if (!notifyAvailable()) return; - let request = Object.assign({}, request_base, { + let request = Object.assign({}, requestBase(), { body: JSON.stringify({account, authSession}), }); setSession(request); - return fetch(notifyUrl(`/login_account`), request).then(r => { + return fetchEx(notifyUrl(`/login_account`), request).then(r => { saveSession(r); return r.json(); }); @@ -50,20 +52,20 @@ export function notifyApiLogin(account, authSession) { export function notifyApiLogout() { if (!notifyAvailable()) return; - let request = Object.assign({}, request_base, { + let request = Object.assign({}, requestBase(), { method: 'get', }); setSession(request); - fetch(notifyUrl(`/logout_account`), request).then(r => { + fetchEx(notifyUrl(`/logout_account`), request).then(r => { saveSession(r); }); } export function getNotifications(account) { if (!notifyAvailable()) return Promise.resolve(null); - let request = Object.assign({}, request_base, {method: 'get'}); + let request = Object.assign({}, requestBase(), {method: 'get'}); setSession(request); - return fetch(notifyUrl(`/counters/@${account}`), request).then(r => { + return fetchEx(notifyUrl(`/counters/@${account}`), request).then(r => { saveSession(r); return r.json(); }).then(res => { @@ -73,10 +75,10 @@ export function getNotifications(account) { export function markNotificationRead(account, fields) { if (!notifyAvailable()) return Promise.resolve(null); - let request = Object.assign({}, request_base, {method: 'put', mode: 'cors'}); + let request = Object.assign({}, requestBase(), {method: 'put', mode: 'cors'}); setSession(request); const fields_str = fields.join(','); - return fetch(notifyUrl(`/counters/@${account}/${fields_str}`), request).then(r => { + return fetchEx(notifyUrl(`/counters/@${account}/${fields_str}`), request).then(r => { saveSession(r); return r.json(); }).then(res => { @@ -88,9 +90,9 @@ export async function notificationSubscribe(account, scopes = 'message', sidKey if (!notifyAvailable()) return; if (window[sidKey]) return; try { - let request = Object.assign({}, request_base, {method: 'get'}); + let request = Object.assign({}, requestBase(), {method: 'get'}); setSession(request); - let response = await fetch(notifyUrl(`/subscribe/@${account}/${scopes}`), request); + let response = await fetchEx(notifyUrl(`/subscribe/@${account}/${scopes}`), request); const result = await response.json(); if (response.ok) { saveSession(response); @@ -113,9 +115,9 @@ export async function notificationUnsubscribe(account, sidKey = '__subscriber_id let url = notifyUrl(`/unsubscribe/@${account}/${window[sidKey]}`); let response; try { - let request = Object.assign({}, request_base, {method: 'get'}); + let request = Object.assign({}, requestBase(), {method: 'get'}); setSession(request); - response = await fetch(url, request); + response = await fetchEx(url, request); if (response.ok) { saveSession(response); } @@ -139,9 +141,12 @@ export async function notificationTake(account, removeTaskIds, forEach, sidKey = url += '/' + removeTaskIds; let response; try { - let request = Object.assign({}, request_base, {method: 'get'}); + let request = Object.assign({}, requestBase(), {method: 'get'}); setSession(request); - response = await fetch(url, request); + response = await fetchEx(url, { + ...request, + timeout: null + }) if (response.ok) { saveSession(response); } @@ -170,6 +175,142 @@ export async function notificationTake(account, removeTaskIds, forEach, sidKey = } } +function getSubId(author, permlink) { + return author + '|' + permlink +} + +export async function subscribePost(account, author, permlink) { + const entity_id = getSubId(author, permlink) + + const request = Object.assign({}, requestBase(), { + body: JSON.stringify({account, entity_id}), + }) + try { + setSession(request) + const response = await fetchEx(notifyUrl(`/subs/subscribe`), request) + const result = await response.json() + if (result.status === 'ok') { + } else { + throw new Error(response.status + ': ' + result.error) + } + } catch (ex) { + console.error(ex) + throw ex + } +} + +export async function unsubscribePost(account, author, permlink) { + const entity_id = getSubId(author, permlink) + + const request = Object.assign({}, requestBase(), { + method: 'DELETE' + }) + try { + setSession(request) + const response = await fetchEx(notifyUrl(`/subs/@${account}/${entity_id}/unsubscribe`), request) + const result = await response.json() + if (result.status === 'ok') { + console.log('unsi', result) + } else { + throw new Error(response.status + ': ' + result.error) + } + } catch (ex) { + console.error(ex) + throw ex + } +} + +export async function markCommentsRead(account, author, permlink) { + const entity_id = getSubId(author, permlink) + + const request = Object.assign({}, requestBase(), { + method: 'PATCH' + }) + try { + setSession(request) + const response = await fetchEx(notifyUrl(`/subs/@${account}/${entity_id}`), request) + const result = await response.json() + if (result.status === 'ok') { + console.log('mark_read', result) + } else { + throw new Error(response.status + ': ' + result.error) + } + } catch (ex) { + console.error(ex) + throw ex + } +} + +export async function getSubs(account, from, limit) { + const params = new URLSearchParams() + if (from) params.set('from', from) + if (limit) params.set('limit', limit) + try { + let request = Object.assign({}, requestBase(), {method: 'get'}); + setSession(request) + const url = notifyUrl(`/subs/@${account}`) + '?' + params.toString() + const response = await fetchEx(url, request) + const result = await response.json() + if (result.status === 'ok') { + return result + } else { + throw new Error(response.status + ': ' + result.error) + } + } catch (ex) { + console.error(ex) + throw ex + } +} + +export async function getEvents(account, author, permlink) { + const entity_id = getSubId(author, permlink) + try { + let request = Object.assign({}, requestBase(), {method: 'get'}); + setSession(request) + const url = notifyUrl(`/subs/@${account}/${entity_id}/events`) + const response = await fetchEx(url, { + ...request, + timeout: 1500 + }) + const result = await response.json() + if (result.status === 'ok') { + return result.result || [] + } else { + throw new Error(response.status + ': ' + result.error) + } + } catch (ex) { + console.error(ex) + throw ex + } +} + +export function isHighlight() { + if (!process.env.BROWSER) { + return false + } + let pa + try { + pa = new URLSearchParams(window.location.search) + } catch (err) { + console.error(err) + return false + } + return pa.has('highlight') +} + +export function addHighlight(url) { + if (!process.env.BROWSER) { + return url + } + const [ main, hash ] = url.split('#') + const [ body, search ] = main.split('?') + if (!search) { + return body + '?highlight=1' + (hash ? '#' + hash : '') + } else { + return body + '?' + search + '&highlight=1' + (hash ? '#' + hash : '') + } +} + if (process.env.BROWSER) { window.getNotifications = getNotifications; window.markNotificationRead = markNotificationRead; diff --git a/app/utils/SearchClient.js b/app/utils/SearchClient.js index e2abc13c9..f41bfc7ab 100644 --- a/app/utils/SearchClient.js +++ b/app/utils/SearchClient.js @@ -1,9 +1,9 @@ import { Headers } from 'cross-fetch' import diff_match_patch from 'diff-match-patch' import {config, api} from 'golos-lib-js'; +import { fetchEx } from 'golos-lib-js/lib/utils' import { detransliterate } from 'app/utils/ParsersAndFormatters' -import fetchWithTimeout from 'shared/fetchWithTimeout' const dmp = new diff_match_patch() @@ -159,7 +159,8 @@ export async function sendSearchRequest(_index, _type, sr, timeoutMsec = 10000) let body = sr.build ? sr.build() : sr let url = new URL($STM_Config.elastic_search.url); url += _index + '/' + _type + '/_search?pretty' - const response = await fetchWithTimeout(url, timeoutMsec, { + const response = await fetchEx(url, { + timeout: timeoutMsec, method: 'post', headers: new Headers({ 'Authorization': 'Basic ' + btoa($STM_Config.elastic_search.login + ':' + $STM_Config.elastic_search.password), diff --git a/package.json b/package.json index a87ce16ec..f5898f7bd 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "foundation-sites": "^6.4.3", "fs-extra": "^10.0.1", "git-rev-sync": "^1.12.0", - "golos-lib-js": "^0.9.45", + "golos-lib-js": "^0.9.47", "history": "^2.0.0-rc2", "immutable": "^3.8.2", "intl": "^1.2.5", diff --git a/server/api/general.js b/server/api/general.js index c4e93147c..1cee85d1c 100644 --- a/server/api/general.js +++ b/server/api/general.js @@ -7,7 +7,6 @@ import coBody from 'co-body'; import {PublicKey, Signature, hash} from 'golos-lib-js/lib/auth/ecc'; import {api, broadcast} from 'golos-lib-js'; import { getDynamicGlobalProperties } from 'app/utils/APIWrapper' -import useGetAddressHandler from 'server/api/uia_address' export default function useGeneralApi(app) { const router = koa_router({prefix: '/api/v1'}); @@ -97,6 +96,4 @@ export default function useGeneralApi(app) { router.post('/page_view', koaBody, function *() { this.body = JSON.stringify({views: 1}); }); - - useGetAddressHandler(router) } diff --git a/server/api/uia_address.js b/server/api/uia_address.js deleted file mode 100644 index e455f3101..000000000 --- a/server/api/uia_address.js +++ /dev/null @@ -1,91 +0,0 @@ -import { api } from 'golos-lib-js' - -import { rateLimitReq } from 'server/utils/misc' -import fetchWithTimeout from 'shared/fetchWithTimeout' - -export default function useGetAddressHandler(app) { - app.get('/uia_address/:symbol/:account', function *() { - let symbol - let accName - const errResp = (errorName, logData, errorData) => { - console.error('/uia_address', errorName, symbol, ...logData) - this.body = { status: 'err', - error: errorName, - symbol, - error_data: errorData, - } - } - try { - if (rateLimitReq(this, this.req)) - return errResp('too_many_requests', [symbol + '/' + accName]) - - symbol = this.params.symbol - if (!symbol) - return errResp('no_symbol_parameter_in_query') - - accName = this.params.account - if (!accName) - return errResp('no_account_parameter_in_query') - let accs - try { - accs = yield api.getAccounts([accName]) - } catch (err) { - return errResp('blockchain_unavailable', [err]) - } - if (!accs[0]) - return errResp('no_such_golos_account', [accName]) - - let assets; - try { - assets = yield api.getAssetsAsync('', [ - symbol, - ], '', '20', 'by_symbol_name') - } catch (err) { - return errResp('blockchain_unavailable', [err]) - } - if (!assets[0]) - return errResp('no_such_asset') - - let meta = assets[0].json_metadata - try { - meta = JSON.parse(meta) - } catch (err) { - return errResp('your_asset_has_wrong_json_metadata', [meta]) - } - - let apiURL = meta.deposit && meta.deposit.to_api - if (!apiURL) - return errResp('no_deposit_settings_in_your_asset', [meta]) - if (!apiURL.includes('')) - return errResp('url_template_not_contains_place_for_account_name', [meta]) - apiURL = apiURL.replace(//g, accName) - - let resp - try { - resp = yield fetchWithTimeout(apiURL, 10000) - } catch (err) { - return errResp('cannot_connect_gateway', [meta.deposit, err]) - } - try { - resp = yield resp.text() - } catch (err) { - return errResp('cannot_get_address_from_gateway', [meta.deposit, err]) - } - try { - resp = JSON.parse(resp) - } catch (err) { - resp = resp.substring(0, 100) - return errResp('invalid_json_from_gateway', [meta.deposit, err], resp) - } - - if (!resp.address) - return errResp('no_address_field_in_response', [resp], resp) - this.body = { - status: 'ok', - address: resp.address, - } - } catch (err) { - return errResp('internal_error', err) - } - }) -} diff --git a/shared/fetchWithTimeout.js b/shared/fetchWithTimeout.js deleted file mode 100644 index 7369f1554..000000000 --- a/shared/fetchWithTimeout.js +++ /dev/null @@ -1,10 +0,0 @@ -import fetch from 'cross-fetch' - -export default async function fetchWithTimeout(url, timeoutMsec, opts) { - const controller = new AbortController() - setTimeout(() => controller.abort(), timeoutMsec) - return await fetch(url, { - signal: controller.signal, - ...opts - }) -} diff --git a/shared/getUIAAddress.js b/shared/getUIAAddress.js deleted file mode 100644 index 55b08c9d4..000000000 --- a/shared/getUIAAddress.js +++ /dev/null @@ -1,57 +0,0 @@ -import { api } from 'golos-lib-js' - -import fetchWithTimeout from 'shared/fetchWithTimeout' - -export default async function getUIAAddress(accName, symbol, okResp, errResp) { - try { - let assets; - try { - assets = await api.getAssetsAsync('', [ - symbol, - ], '', '20', 'by_symbol_name') - } catch (err) { - return errResp('blockchain_unavailable', [err]) - } - if (!assets[0]) - return errResp('no_such_asset') - - let meta = assets[0].json_metadata - try { - meta = JSON.parse(meta) - } catch (err) { - return errResp('your_asset_has_wrong_json_metadata', [meta]) - } - - let apiURL = meta.deposit && meta.deposit.to_api - if (!apiURL) - return errResp('no_deposit_settings_in_your_asset', [meta]) - if (!apiURL.includes('')) - return errResp('url_template_not_contains_place_for_account_name', [meta]) - apiURL = apiURL.replace(//g, accName) - - let resp - try { - resp = await fetchWithTimeout(apiURL, 10000) - } catch (err) { - return errResp('cannot_connect_gateway', [meta.deposit, err]) - } - try { - resp = await resp.text() - } catch (err) { - return errResp('cannot_get_address_from_gateway', [meta.deposit, err]) - } - try { - resp = JSON.parse(resp) - } catch (err) { - resp = resp.substring(0, 100) - return errResp('invalid_json_from_gateway', [meta.deposit, err], resp) - } - - if (!resp.address) - return errResp('no_address_field_in_response', [resp], resp) - - return okResp(resp.address) - } catch (err) { - return errResp('internal_error', err) - } -} diff --git a/yarn.lock b/yarn.lock index 00bed9407..074f9a5d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5945,9 +5945,9 @@ es-abstract@^1.17.4, es-abstract@^1.18.0, es-abstract@^1.18.0-next.1, es-abstrac unbox-primitive "^1.0.1" es-abstract@^1.19.5, es-abstract@^1.20.0: - version "1.20.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.3.tgz#90b143ff7aedc8b3d189bcfac7f1e3e3f81e9da1" - integrity sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw== + version "1.20.4" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.4.tgz#1d103f9f8d78d4cf0713edcd6d0ed1a46eed5861" + integrity sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" @@ -5959,7 +5959,7 @@ es-abstract@^1.19.5, es-abstract@^1.20.0: has-property-descriptors "^1.0.0" has-symbols "^1.0.3" internal-slot "^1.0.3" - is-callable "^1.2.6" + is-callable "^1.2.7" is-negative-zero "^2.0.2" is-regex "^1.1.4" is-shared-array-buffer "^1.0.2" @@ -7263,10 +7263,10 @@ globule@^1.0.0: lodash "~4.17.10" minimatch "~3.0.2" -golos-lib-js@^0.9.45: - version "0.9.45" - resolved "https://registry.yarnpkg.com/golos-lib-js/-/golos-lib-js-0.9.45.tgz#63276e8eb495b7d6353607ec074447f93e3a60ca" - integrity sha512-L5V4oLOHviqko3x7aaod5wbtE7fF164EjtPxoBvnYWcy5PhAReMiXM6v4l2uXhZyR1Z06b5O/ceNvLqAD5ZnHQ== +golos-lib-js@^0.9.47: + version "0.9.47" + resolved "https://registry.yarnpkg.com/golos-lib-js/-/golos-lib-js-0.9.47.tgz#5a6868dadf230c4bdd7693a5e3a8d9814d997db7" + integrity sha512-svitIWlx6eNYURFBCm9w85qNRhs3QQ9C1ti60HJb4HNyjD3Ixr2/8Bj7VlhFTicMFzuqMPxQYPeemtrmwMAFtw== dependencies: abort-controller "^3.0.0" assert "^2.0.0" @@ -8207,7 +8207,7 @@ is-callable@^1.2.4: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== -is-callable@^1.2.6: +is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== @@ -12760,11 +12760,16 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== -regenerator-runtime@^0.13.7, regenerator-runtime@^0.13.9: +regenerator-runtime@^0.13.7: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.13.9: + version "0.13.10" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz#ed07b19616bcbec5da6274ebc75ae95634bfc2ee" + integrity sha512-KepLsg4dU12hryUO7bp/axHAKvwGOCV0sGloQtpagJ12ai+ojVDqkeGSiRX1zlq+kjIMZ1t7gpze+26QqtdGqw== + regenerator-transform@^0.14.2: version "0.14.5" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.5.tgz#c98da154683671c9c4dcb16ece736517e1b7feb4" @@ -15115,15 +15120,14 @@ util@^0.11.0: inherits "2.0.3" util@^0.12.0: - version "0.12.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253" - integrity sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw== + version "0.12.5" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.5.tgz#5f17a6059b73db61a875668781a1c2b136bd6fbc" + integrity sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA== dependencies: inherits "^2.0.3" is-arguments "^1.0.4" is-generator-function "^1.0.7" is-typed-array "^1.1.3" - safe-buffer "^5.1.2" which-typed-array "^1.1.2" utila@~0.4: From 549e89e372766c5d15fb8f1b3d2cfb9975e8869d Mon Sep 17 00:00:00 2001 From: 1aerostorm Date: Fri, 21 Oct 2022 02:20:48 +0000 Subject: [PATCH 03/17] Post views --- app/components/cards/PostFull.jsx | 11 ++++++++++ app/components/pages/Post.jsx | 36 ++++++++++++++++++------------- app/locales/en.json | 5 +++++ app/locales/ru-RU.json | 5 +++++ app/redux/FetchDataSaga.js | 10 ++++++++- app/utils/NotifyApiClient.js | 25 +++++++++++++++++++++ 6 files changed, 76 insertions(+), 16 deletions(-) diff --git a/app/components/cards/PostFull.jsx b/app/components/cards/PostFull.jsx index 201858ccb..4ea250ffd 100644 --- a/app/components/cards/PostFull.jsx +++ b/app/components/cards/PostFull.jsx @@ -494,6 +494,17 @@ class PostFull extends React.Component { ) : null} + + + + {content.views} + + { + saveSession(r) + return r.json() + }) +} + +export function notifyGetViews(commentIds) { + if (!notifyAvailable()) return + let request = Object.assign({}, requestBase(), { + body: JSON.stringify({ items: commentIds }), + timeout: 1500 + }) + setSession(request) + return fetchEx(notifyUrl(`/stats/views`), request).then(r => { + saveSession(r) + return r.json() + }) +} + export function isHighlight() { if (!process.env.BROWSER) { return false From 3bca3522a95de245a6d2ff98bad0a068bc773a03 Mon Sep 17 00:00:00 2001 From: 1aerostorm Date: Fri, 21 Oct 2022 03:18:47 +0000 Subject: [PATCH 04/17] Fix authorization - onlyblog --- app/components/pages/Post.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/pages/Post.jsx b/app/components/pages/Post.jsx index ff775dd0f..a652c3c1d 100644 --- a/app/components/pages/Post.jsx +++ b/app/components/pages/Post.jsx @@ -317,7 +317,7 @@ class Post extends React.Component { return this._renderOnlyApp() } if (stats.isOnlyblog) { - if (!following && (typeof(localStorage) === 'undefined' || !session.load().currentName)) { + if (!following && (typeof(localStorage) === 'undefined' || session.load().currentName)) { return this._renderLoadingStub() } else if (!following || (!following.includes(dis.get('author')) && From c9db39805f6e11c808411a8210d84d508af6330a Mon Sep 17 00:00:00 2001 From: 1aerostorm Date: Fri, 21 Oct 2022 05:09:42 +0000 Subject: [PATCH 05/17] Improve NSFW --- app/components/cards/Comment.jsx | 3 +- app/components/cards/PostFull.jsx | 3 +- app/components/cards/PostSummary.jsx | 92 ++++++++++++++++---- app/components/cards/PostSummary.scss | 11 +-- app/components/elements/PostSummaryThumb.jsx | 59 ++----------- app/components/pages/Post.jsx | 3 +- app/locales/ru-RU.json | 2 +- app/utils/blacklist.js | 18 ++++ server/index.js | 11 ++- server/server.js | 3 +- 10 files changed, 123 insertions(+), 82 deletions(-) create mode 100644 app/utils/blacklist.js diff --git a/app/components/cards/Comment.jsx b/app/components/cards/Comment.jsx index 0a6596fd7..fc6d6372a 100644 --- a/app/components/cards/Comment.jsx +++ b/app/components/cards/Comment.jsx @@ -7,6 +7,7 @@ import cn from 'classnames'; import tt from 'counterpart'; import { FormattedPlural } from 'react-intl'; +import { isBlocked } from 'app/utils/blacklist' import { sortComments } from 'app/utils/comments'; import user from 'app/redux/User'; import transaction from 'app/redux/Transaction'; @@ -158,7 +159,7 @@ class CommentImpl extends PureComponent { let body = null; let controls = null; - if ($STM_Config.blocked_users.includes(comment.author)) { + if (isBlocked(comment.author, $STM_Config.blocked_users)) { return null; } else if (!this.state.collapsed && !hideBody) { body = ( diff --git a/app/components/cards/PostFull.jsx b/app/components/cards/PostFull.jsx index 4ea250ffd..26c8bc797 100644 --- a/app/components/cards/PostFull.jsx +++ b/app/components/cards/PostFull.jsx @@ -21,6 +21,7 @@ import Author from 'app/components/elements/Author'; import Userpic from 'app/components/elements/Userpic'; import PostFormLoader from 'app/components/modules/PostForm/loader'; import CommentFormLoader from 'app/components/modules/CommentForm/loader'; +import { isBlocked } from 'app/utils/blacklist' import { getEditDraftPermLink } from 'app/utils/postForm'; import { proxifyImageUrl } from 'app/utils/ProxifyUrl'; import PostSummaryThumb from 'app/components/elements/PostSummaryThumb'; @@ -346,7 +347,7 @@ class PostFull extends React.Component { const url = `/${category}/@${author}/${permlink}`; let contentBody; - if ($STM_Config.blocked_posts.includes(url) && !username) { + if (isBlocked(url, $STM_Config.blocked_posts) && !username) { contentBody = tt( 'postfull_jsx.this_post_is_not_available_due_to_breach_of_legislation' ); diff --git a/app/components/cards/PostSummary.jsx b/app/components/cards/PostSummary.jsx index 9b244fe5e..8f8c43cf1 100644 --- a/app/components/cards/PostSummary.jsx +++ b/app/components/cards/PostSummary.jsx @@ -9,6 +9,7 @@ import tt from 'counterpart' import { CHANGE_IMAGE_PROXY_TO_STEEMIT_TIME } from 'app/client_config' import Author from 'app/components/elements/Author' import Icon from 'app/components/elements/Icon' +import DialogManager from 'app/components/elements/common/DialogManager' import MuteAuthorInNew from 'app/components/elements/MuteAuthorInNew' import PinPost from 'app/components/elements/PinPost' import PostSummaryThumb from 'app/components/elements/PostSummaryThumb' @@ -22,6 +23,8 @@ import g from 'app/redux/GlobalReducer' import transaction from 'app/redux/Transaction' import user from 'app/redux/User' import {immutableAccessor} from 'app/utils/Accessors' +import { authRegisterUrl, } from 'app/utils/AuthApiClient' +import { isBlocked } from 'app/utils/blacklist' import extractContent from 'app/utils/ExtractContent' import {authorNameAndRep} from 'app/utils/ComponentFormatters' import { addHighlight, unsubscribePost } from 'app/utils/NotifyApiClient' @@ -37,9 +40,13 @@ function isModifiedEvent(event) { return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) } -function navigate(e, onClick, post, url, isForum, isSearch) { +function navigate(e, onClick, post, url, isForum, isSearch, warn) { if (isForum || isSearch) return; + if (warn) { + e.preventDefault() + return + } if (isModifiedEvent(e) || !isLeftClickEvent(e)) return; e.preventDefault() if (onClick) onClick(post, url); @@ -64,7 +71,6 @@ class PostSummary extends React.Component { constructor() { super(); this.state = {revealNsfw: false} - this.onRevealNsfw = this.onRevealNsfw.bind(this) } shouldComponentUpdate(props, state) { @@ -80,9 +86,26 @@ class PostSummary extends React.Component { props.hide !== this.props.hide } - onRevealNsfw(e) { - e.preventDefault(); - this.setState({revealNsfw: true}) + componentDidUpdate(prevProps) { + if (this.state.revealNsfw && !this.props.username && prevProps.username) { + this.setState({ revealNsfw: false }) + } + } + + onClick = (e) => { + if (this.isNsfwWarn()) { + e.preventDefault() + const { username, showLogin } = this.props + if (!username) { + DialogManager.info( + {tt('poststub.login')} +  {tt('g.or')}  + {tt('poststub.sign_up')}. + ) + return + } + this.setState({revealNsfw: true}) + } } onDeleteReblog = (account, post) => { @@ -113,6 +136,13 @@ class PostSummary extends React.Component { this.props.unsubscribe(username, author, permlink) } + isNsfwWarn = () => { + const { content, nsfwPref, username } = this.props + const { isNsfw, } = content.get('stats', Map()).toJS() + const myPost = username === content.get('author') + return (isNsfw && nsfwPref === 'warn' && !myPost && !this.state.revealNsfw) + } + render() { const {currentCategory, thumbSize, ignore, onClick} = this.props; const {post, content, pending_payout, total_payout, cashout_time, blockEye} = this.props; @@ -122,7 +152,7 @@ class PostSummary extends React.Component { const myPost = username === content.get('author') - if ($STM_Config.blocked_users.includes(content.get('author'))) { + if (isBlocked(content.get('author'), $STM_Config.blocked_users)) { return null; } @@ -161,6 +191,12 @@ class PostSummary extends React.Component { return null } + const isPublic = currentCategory !== 'blog' && currentCategory !== 'feed' + if (isBlocked(content.get('url'), $STM_Config.blocked_posts) + && (!username || isPublic)) { + return null + } + const p = extractContent(immutableAccessor, content); const nsfwTags = ['nsfw', 'ru--mat', '18+'] let nsfwTitle = nsfwTags[0] @@ -225,11 +261,20 @@ class PostSummary extends React.Component { comments_link = addHighlight(comments_link) } - let content_body =
- navigate(e, onClick, post, title_link_url, is_forum, from_search)}>{desc} + const warn = this.isNsfwWarn() + const filterClasses = [] + if (warn) filterClasses.push('nsfw-text') + + const stubText = warn && !username + + const nsfwStub = 'Not safe for work 18+' + + let content_body = ; - const warn = (isNsfw && nsfwPref === 'warn' && !myPost); const promosumm = tt('g.promoted_post') + content.get('promoted'); let worker_post = content.get('has_worker_request') @@ -242,8 +287,8 @@ class PostSummary extends React.Component { const visitedClassName = this.props.visited ? 'PostSummary__post-visited ' : '' let content_title = - navigate(e, onClick, post, title_link_url, is_forum, from_search)}> - {title_text} + navigate(e, onClick, post, title_link_url, is_forum, from_search, warn)}> + {stubText ? nsfwStub : title_text} {isOnlyblog && {tt('g.for_followers')}} {isOnlyapp && {tt('g.only_app')}} @@ -254,7 +299,7 @@ class PostSummary extends React.Component { // author and category let author_category = - navigate(e, onClick, post, title_link_url, is_forum, from_search)}> + navigate(e, onClick, post, title_link_url, is_forum, from_search, warn)}> {' '} {blockEye && } @@ -275,8 +320,8 @@ class PostSummary extends React.Component {
- if(isNsfw) { - if(nsfwPref === 'hide' && !myPost) { + if(isNsfw && !myPost) { + if(nsfwPref === 'hide') { // user wishes to hide these posts entirely return null; } @@ -300,7 +345,7 @@ class PostSummary extends React.Component { src={url} href={title_link_url} target={link_target} - onClick={e => navigate(e, onClick, post, title_link_url, is_forum, from_search)} /> + onClick={e => navigate(e, onClick, post, title_link_url, is_forum, from_search, warn)} /> } const commentClasses = [] if(gray || ignore) commentClasses.push('downvoted') // rephide @@ -321,7 +366,7 @@ class PostSummary extends React.Component { if (eventCount) { const commFew = tt('comment_jsx.N_comments_2', { N: eventCount }) const commMany = tt('comment_jsx.N_comments', { N: eventCount }) - newReplies = navigate(e, onClick, post, title_link_url, is_forum, from_search)}> + newReplies = navigate(e, onClick, post, title_link_url, is_forum, from_search, warn)}> {'+'} return ( -
+
{total_search} {reblogged_by} -
+
{content_title}
@@ -356,7 +401,7 @@ class PostSummary extends React.Component { {thumb}
-
+
{content_title}
{content_body} @@ -409,5 +454,14 @@ export default connect( successCallback, errorCallback, })) }, + showLogin: e => { + if (e) e.preventDefault() + try { + document.getElementsByClassName('DialogManager__shade')[0].click() + } catch (err) { + console.error(err) + } + dispatch(user.actions.showLogin()) + }, }) )(PostSummary) diff --git a/app/components/cards/PostSummary.scss b/app/components/cards/PostSummary.scss index 2a580dc98..b801a2b98 100644 --- a/app/components/cards/PostSummary.scss +++ b/app/components/cards/PostSummary.scss @@ -11,11 +11,8 @@ ul.PostsList__summaries { clear: left; @include clearfix; - .PostSummary__nsfw-warning { - border: 1px solid $medium-gray; - border-radius: 0.5rem; - padding: 0.75rem 2rem; - min-height: 80px; + .nsfw-text { + filter: blur(4px); .PostSummary__footer { margin-top: 0.2rem; .Reblog__button {display: none;} @@ -26,6 +23,10 @@ ul.PostsList__summaries { } } + .nsfw-img { + filter: blur(7px); + } + .nsfw_post { color: #C00; border: 1px solid #C00; diff --git a/app/components/elements/PostSummaryThumb.jsx b/app/components/elements/PostSummaryThumb.jsx index 87bd6ad2d..0cb35c651 100644 --- a/app/components/elements/PostSummaryThumb.jsx +++ b/app/components/elements/PostSummaryThumb.jsx @@ -1,10 +1,4 @@ import React from 'react'; -// keep this in memory, no requests -import nsfwBanner from 'app/assets/images/nsfw/light.png' -// import * as pixelate from './utils/effects/close-pixelate' - - -//todo render fallback image by react, not by canvas depending on state? export default class PostSummaryThumb extends React.Component { constructor(props) { @@ -12,62 +6,25 @@ export default class PostSummaryThumb extends React.Component { this.state = {} } - handleImageLoaded() { - if (this.props.isNsfw) { - this.cp = new window.ClosePixelation(this.img, this.canvas) - try { - this.cp.render([ - { - // todo fix author's algorithm - // either loses color channels or disappears completely ) - // only these options render well - resolution: 10, - alpha: 1, - size: 10, - } - ]) - } - catch (e) { - // clear canvas before the fallback image drawing! - this.cp.ctx.clearRect(0, 0, this.cp.width, this.cp.height); - this.cp.ctx.drawImage(this.defaultImage, - this.cp.width / 2 - this.defaultImage.width / 2, - this.cp.height / 2 - this.defaultImage.height / 2, - ) - } - } - } - - handleImageErrored() { - this.setState({imageStatus: 'failed to load'}); - } - render() { - let {visitedClassName, title, body} = this.props; - title = title ? title : ''; - body = body ? body : ''; + let { visitedClassName, title, body, isNsfw } = this.props + title = title || '' + body = body || '' + let className = this.props.mobile ? ('PostSummary__image-mobile ' + visitedClassName) : 'PostSummary__image ' + if (isNsfw) { + className += ' nsfw-img' + } return (
{ this.img = img; }}> - { - this.defaultImage = img; - }} - style={!this.props.isNsfw ? {display: "none"} : {}} - // src={`/images/18_plus.png`} - src={nsfwBanner} - className={'PostSummary__image '} //+ visitedClassName}*!/*/} - > -
{title}
diff --git a/app/components/pages/Post.jsx b/app/components/pages/Post.jsx index a652c3c1d..0ed591dfd 100644 --- a/app/components/pages/Post.jsx +++ b/app/components/pages/Post.jsx @@ -14,6 +14,7 @@ import LoadingIndicator from 'app/components/elements/LoadingIndicator'; import g from 'app/redux/GlobalReducer' import user from 'app/redux/User' import { authRegisterUrl, } from 'app/utils/AuthApiClient' +import { isBlocked } from 'app/utils/blacklist' import { sortComments } from 'app/utils/comments' import { subscribePost, unsubscribePost, getSubs } from 'app/utils/NotifyApiClient' import shouldComponentUpdate from 'app/utils/shouldComponentUpdate' @@ -344,7 +345,7 @@ class Post extends React.Component {
- if($STM_Config.blocked_users.includes(post.split("/")[0])) { + if (isBlocked(post.split('/')[0], $STM_Config.blocked_users)) { return () } diff --git a/app/locales/ru-RU.json b/app/locales/ru-RU.json index 8ccfaafb5..ebfc51743 100644 --- a/app/locales/ru-RU.json +++ b/app/locales/ru-RU.json @@ -262,7 +262,7 @@ "next": "Следующий", "no": "Нет", "nothing_yet": "Пока ничего нет", - "ok": "Да", + "ok": "OK", "older": "Старее", "or": "или", "order_placed": "Заказ размещен", diff --git a/app/utils/blacklist.js b/app/utils/blacklist.js new file mode 100644 index 000000000..9ec5a43e7 --- /dev/null +++ b/app/utils/blacklist.js @@ -0,0 +1,18 @@ +import ByteBuffer from 'bytebuffer' + +function toHex(str) { + return '3'+ByteBuffer.fromUTF8(str).toHex()+'a' +} + +function fromHex(str) { + return ByteBuffer.fromHex(str).toUTF8() +} + +export function packBlacklist(arr) { + return arr.map(item => toHex(item)) +} + +export function isBlocked(who, where) { + const hex = toHex(who) + return where.includes(hex) +} diff --git a/server/index.js b/server/index.js index d09eefc27..854528fa0 100755 --- a/server/index.js +++ b/server/index.js @@ -1,6 +1,7 @@ import config from 'config'; import * as golos from 'golos-lib-js'; const version = require('./version'); +const { packBlacklist } = require('../app/utils/blacklist') delete process.env.BROWSER; @@ -12,6 +13,12 @@ const ROOT = path.join(__dirname, '..'); process.env.NODE_PATH = path.resolve(__dirname, '..'); require('module').Module._initPaths(); +let blocked_users = config.get('blocked_users') +blocked_users = packBlacklist(blocked_users) + +let blocked_posts = config.get('blocked_posts') +blocked_posts = packBlacklist(blocked_posts) + global.$STM_Config = { ws_connection_client: config.get('ws_connection_client'), hide_comment_neg_rep: config.get('hide_comment_neg_rep'), @@ -28,8 +35,8 @@ global.$STM_Config = { messenger_service: config.get('messenger_service'), apidex_service: config.get('apidex_service'), forums: config.get('forums'), - blocked_users: config.get('blocked_users'), - blocked_posts: config.get('blocked_posts'), + blocked_users, + blocked_posts, ui_version: version || '1.0-unknown', }; diff --git a/server/server.js b/server/server.js index 4c2de7d84..e0e1d8c70 100644 --- a/server/server.js +++ b/server/server.js @@ -20,6 +20,7 @@ import config from 'config'; import { routeRegex } from 'app/ResolveRoute'; import secureRandom from 'secure-random'; import { APP_NAME_UP } from 'app/client_config'; +import { isBlocked } from 'app/utils/blacklist' console.log('application server starting, please wait.'); @@ -93,7 +94,7 @@ app.use(function*(next) { } else { userCheck = p.split("/")[1].slice(1); } - if ($STM_Config.blocked_users.includes(userCheck)) { + if (isBlocked(userCheck, $STM_Config.blocked_users)) { console.log('Illegal content user found blocked', `@${userCheck}`); this.status = 451; return; From 941ff4a980339f9f3062406e99f5c2d9cd82873a Mon Sep 17 00:00:00 2001 From: 1aerostorm Date: Sat, 22 Oct 2022 00:44:27 +0000 Subject: [PATCH 06/17] Fix Welcome banner --- app/components/App.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/App.jsx b/app/components/App.jsx index 0dc608132..3dd0c0c2f 100644 --- a/app/components/App.jsx +++ b/app/components/App.jsx @@ -73,7 +73,7 @@ class App extends React.Component { const n = nextProps; return ( p.location !== n.location || - p.visitor !== n.visitor || + p.new_visitor !== n.new_visitor || p.flash !== n.flash || this.state !== nextState || p.nightmodeEnabled !== n.nightmodeEnabled From 50099309c3d3aed95b7a9c787818ef582de93a97 Mon Sep 17 00:00:00 2001 From: 1aerostorm Date: Tue, 25 Oct 2022 02:17:23 +0000 Subject: [PATCH 07/17] Improve authorization --- app/components/all.scss | 1 - app/components/elements/ClaimInvite.jsx | 173 --------- app/components/elements/ConvertToSteem.jsx | 251 ------------- app/components/elements/CreateInvite.jsx | 370 ------------------- app/components/elements/Invites.jsx | 36 -- app/components/elements/WitnessSettings.jsx | 191 ---------- app/components/elements/WitnessSettings.scss | 36 -- app/components/modules/Dialogs.jsx | 14 - app/components/modules/Header.jsx | 2 - app/components/modules/PromotePost.jsx | 1 + app/components/modules/WitnessProps.jsx | 263 ------------- app/components/modules/WitnessProps.scss | 15 - app/components/pages/UserProfile.scss | 75 ---- app/locales/en.json | 11 - app/locales/ru-RU.json | 11 - app/redux/AuthSaga.js | 11 +- app/redux/TransactionSaga.js | 5 +- app/redux/User.js | 21 -- app/redux/UserSaga.js | 36 +- package.json | 2 +- yarn.lock | 20 +- 21 files changed, 26 insertions(+), 1519 deletions(-) delete mode 100644 app/components/elements/ClaimInvite.jsx delete mode 100644 app/components/elements/ConvertToSteem.jsx delete mode 100644 app/components/elements/CreateInvite.jsx delete mode 100644 app/components/elements/Invites.jsx delete mode 100644 app/components/elements/WitnessSettings.jsx delete mode 100644 app/components/elements/WitnessSettings.scss delete mode 100644 app/components/modules/WitnessProps.jsx delete mode 100644 app/components/modules/WitnessProps.scss diff --git a/app/components/all.scss b/app/components/all.scss index 015247c32..9332b2c5f 100644 --- a/app/components/all.scss +++ b/app/components/all.scss @@ -37,7 +37,6 @@ @import "./elements/VerticalMenu"; @import "./elements/VotesAndComments"; @import "./elements/Voting"; -@import "./elements/WitnessSettings"; @import "./elements/common/YoutubePlayer/YoutubePlayer"; @import "./elements/common/TelegramPlayer/TelegramPlayer"; @import "./elements/common/Button/index"; diff --git a/app/components/elements/ClaimInvite.jsx b/app/components/elements/ClaimInvite.jsx deleted file mode 100644 index 5177dd3a3..000000000 --- a/app/components/elements/ClaimInvite.jsx +++ /dev/null @@ -1,173 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import shouldComponentUpdate from 'app/utils/shouldComponentUpdate' -import {countDecimals, formatAmount} from 'app/utils/ParsersAndFormatters'; -import {validate_account_name} from 'app/utils/ChainValidation' -import g from 'app/redux/GlobalReducer' -import {connect} from 'react-redux'; -import transaction from 'app/redux/Transaction' -import user from 'app/redux/User'; -import tt from 'counterpart'; -import reactForm from 'app/utils/ReactForm'; -import {PrivateKey} from 'golos-lib-js/lib/auth/ecc'; -import LoadingIndicator from 'app/components/elements/LoadingIndicator'; - -class ClaimInvite extends Component { - static propTypes = { - // HTML - account: PropTypes.object.isRequired, - // Redux - isMyAccount: PropTypes.bool.isRequired, - accountName: PropTypes.string.isRequired, - } - - constructor(props) { - super() - this.shouldComponentUpdate = shouldComponentUpdate(this, 'ClaimInvite') - this.state = { - errorMessage: '', - successMessage: '', - } - this.initForm(props) - } - - initForm(props) { - const fields = ['invite_secret']; - const validateSecret = (secret) => { - try { - PrivateKey.fromWif(secret); - return null; - } catch (e) { - return tt('invites_jsx.claim_wrong_secret'); - } - }; - reactForm({ - name: 'invite', - instance: this, fields, - initialValues: {}, - validation: values => ({ - invite_secret: - ! values.invite_secret ? tt('g.required') : validateSecret(values.invite_secret) - }) - }) - this.handleSubmitForm = - this.state.invite.handleSubmit(args => this.handleSubmit(args)) - } - - onChangeInviteSecret = (e) => { - const {value} = e.target - this.state.invite_secret.props.onChange(value.trim()) - } - - handleSubmit = ({updateInitialValues}) => { - const {claimInvite, accountName} = this.props - const {invite_secret} = this.state - this.setState({loading: true}); - claimInvite({invite_secret, accountName, - errorCallback: (e) => { - if (e === 'Canceled') { - this.setState({ - loading: false, - errorMessage: '' - }) - } else { - console.log('claimInvite ERROR', e) - this.setState({ - loading: false, - errorMessage: e.includes('Missing') ? tt('invites_jsx.claim_wrong_secret_fatal') : tt('g.server_returned_error') - }) - } - }, - successCallback: () => { - this.setState({ - loading: false, - errorMessage: '', - successMessage: tt('invites_jsx.success_claim'), - }) - // remove successMessage after a while - setTimeout(() => this.setState({successMessage: ''}), 4000) - }}) - } - - render() { - const {props: {account, isMyAccount}} = this - const {invite_secret, loading, successMessage, errorMessage} = this.state - const {submitting, valid} = this.state.invite - - return (
-
-
-
-

{tt('invites_jsx.claim_invite')}

-
-
- -
-
- {tt('invites_jsx.private_key')} -
- this.onChangeInviteSecret(e)} - /> -
- {invite_secret.touched && invite_secret.blur && invite_secret.error && -
{invite_secret.error} 
- } -
-
- -
-
- {loading &&
} - {!loading && } - {' '}{ - errorMessage - ? {errorMessage} - : successMessage - ? {successMessage} - : null - } -
-
-
-
-
) - } -} - -export default connect( - (state, ownProps) => { - const {account} = ownProps - const accountName = account.get('name') - const current = state.user.get('current') - const username = current && current.get('username') - const isMyAccount = username === accountName - return {...ownProps, isMyAccount, accountName} - }, - dispatch => ({ - claimInvite: ({ - invite_secret, accountName, successCallback, errorCallback - }) => { - const operation = { - initiator: accountName, - receiver: accountName, - invite_secret: invite_secret.value - } - - const success = () => { - dispatch(user.actions.getAccount()) - successCallback() - } - - dispatch(transaction.actions.broadcastOperation({ - type: 'invite_claim', - accountName, - operation, - successCallback: success, - errorCallback - })) - } - }) -)(ClaimInvite) diff --git a/app/components/elements/ConvertToSteem.jsx b/app/components/elements/ConvertToSteem.jsx deleted file mode 100644 index f00f04050..000000000 --- a/app/components/elements/ConvertToSteem.jsx +++ /dev/null @@ -1,251 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React from 'react' -import ReactDOM from 'react-dom'; -import { connect, } from 'react-redux'; -import { Formik, Field, ErrorMessage, } from 'formik'; -import transaction from 'app/redux/Transaction' -import shouldComponentUpdate from 'app/utils/shouldComponentUpdate' -import Icon from 'app/components/elements/Icon' -import TransactionError from 'app/components/elements/TransactionError' -import LoadingIndicator from 'app/components/elements/LoadingIndicator' -import tt from 'counterpart'; -import { DEBT_TICKER, LIQUID_TOKEN, LIQUID_TICKER } from 'app/client_config'; -import { Asset } from 'golos-lib-js/lib/utils'; - -function floatToAsset(value, from) { - value = parseFloat(value); - if (isNaN(value)) { - return Asset(0, 3, from); - } - value = value.toFixed(3); - return Asset(value + ' ' + from); -} - -function calcFee(value, cprops) { - const percent = cprops ? cprops.toJS().convert_fee_percent : 0; - const fee = value.mul(parseInt(percent)).div(10000); - return fee; -} - -class ConvertToSteem extends React.Component { - constructor(props) { - super() - const { from, to } = props; - this.state = { - fee: Asset(0, 3, from), - toAmount: Asset(0, 3, to), - }; - this.amtRef = React.createRef(); - } - - componentDidMount() { - this.amtRef.current.focus(); - } - - shouldComponentUpdate = shouldComponentUpdate(this, 'ConvertToSteem') - - validate = (values) => { - const { maxBalance, from, cprops, } = this.props; - const errors = {}; - if (values.amount) { - if (isNaN(values.amount) - || parseFloat(values.amount) <= 0) { - errors.amount = tt('g.required'); - } else if (parseFloat(values.amount) > maxBalance) { - errors.amount = tt('g.insufficient_balance'); - } else if (from === 'GOLOS' && !calcFee(floatToAsset(values.amount), cprops).amount) { - errors.amount = tt('converttosteem_jsx.too_low_amount'); - } - } else { - errors.amount = tt('g.required'); - } - return errors; - }; - - _onSubmit = (values, { setSubmitting, }) => { - const { convert, owner, from, to, onClose } = this.props; - const { amount } = values; - const success = () => { - if (onClose) onClose(); - setSubmitting(false); - }; - const error = () => { - setSubmitting(false); - }; - convert(owner, amount, from, to, success, error); - }; - - onAmountChange = (e, values, handle) => { - let value = e.target.value.trim().toLowerCase(); - if (isNaN(value) || parseFloat(value) < 0) { - e.target.value = values.amount || ''; - return; - } - e.target.value = value; - - const { from, to, feed, cprops } = this.props; - let fee = Asset(0, 3, from); - let toAmount = Asset(0, 3, to); - let { base, quote } = feed; - base = Asset(base); - quote = Asset(quote); - value = floatToAsset(parseFloat(value), from); - if (from === 'GOLOS' && cprops) { - fee = calcFee(value, cprops); - value.amount = value.amount - fee.amount; - } - if (value.symbol === base.symbol) { - toAmount = value.mul(quote).div(base); - } else { - toAmount = value.mul(base).div(quote); - } - toAmount.symbol = to; - this.setState({ toAmount, fee, }); - - return handle(e); - }; - - render() { - const DEBT_TOKEN = tt('token_names.DEBT_TOKEN') - - const { from, to, cprops, onClose, } = this.props - const { fee, toAmount, } = this.state - - let feePercent = 0; - if (cprops && from === 'GOLOS') { - feePercent = parseFloat(cprops.get('convert_fee_percent')) / 100; - } - - return ( - - {({ - handleSubmit, isSubmitting, errors, values, handleChange, - }) => ( -
-
-
-

{tt('converttosteem_jsx.title_FROM_TO', {FROM: from, TO: to})}

-

{tt('converttosteem_jsx.this_will_take_days')} - -

-

{tt('converttosteem_jsx.tokens_will_be_unavailable')}

-
-
-
-
{tt('g.amount')}
-
-
- this.amtRef.current = input} - autoComplete='off' - disabled={isSubmitting} - onChange={e => this.onAmountChange(e, values, handleChange)} - /> - - - -
- -
-
-
-
- {feePercent ? - {tt('converttosteem_jsx.fee')} - - {fee.toString()} - {' '} - ({feePercent}%). - : {tt('converttosteem_jsx.no_fee')} - } -
-
-
-
- {tt('converttosteem_jsx.you_will_receive')} - - {tt('g.approximately')} - {' '} - {toAmount.toString()} - . - -
-
-
-
- - {isSubmitting && } -
- - -
-
-
-
- )}
- ) - } -} -export default connect( - // mapStateToProps - (state, ownProps) => { - const { from, to } = ownProps; - const current = state.user.get('current'); - const username = current.get('username'); - const account = state.global.getIn(['accounts', username]); - const balance = account.get('balance'); - const sbd_balance = account.get('sbd_balance'); - const cprops = state.global.get('cprops'); - const max = parseFloat(Asset(from === DEBT_TICKER ? sbd_balance : balance).amountFloat); - return { - ...ownProps, - owner: username, - feed: state.global.get('feed_price').toJS(), - cprops, - maxBalance: max, - }; - }, - // mapDispatchToProps - dispatch => ({ - convert: (owner, amt, from, to, success, error) => { - const amount = [parseFloat(amt).toFixed(3), from].join(' ') - const requestid = Math.floor(Date.now() / 1000) - const conf = tt('postfull_jsx.in_week_convert_DEBT_TOKEN_to_LIQUID_TOKEN', - { amount: amount.split(' ')[0], DEBT_TOKEN: from, LIQUID_TOKEN: to, }) - dispatch(transaction.actions.broadcastOperation({ - type: 'convert', - operation: { - owner, - requestid, - amount, - __config: {title: tt('converttosteem_jsx.confirm_title')} - }, - confirm: conf + '?', - - successCallback: () => { - success() - dispatch({type: 'ADD_NOTIFICATION', payload: - {key: 'convert_sd_to_steem_' + Date.now(), - message: tt('g.order_placed') + ': ' + conf, - dismissAfter: 5000} - }) - }, - errorCallback: () => {error()} - })) - }, - }) -)(ConvertToSteem) diff --git a/app/components/elements/CreateInvite.jsx b/app/components/elements/CreateInvite.jsx deleted file mode 100644 index 02bb13202..000000000 --- a/app/components/elements/CreateInvite.jsx +++ /dev/null @@ -1,370 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import shouldComponentUpdate from 'app/utils/shouldComponentUpdate' -import {countDecimals, formatAsset, formatAmount} from 'app/utils/ParsersAndFormatters'; -import g from 'app/redux/GlobalReducer' -import {connect} from 'react-redux'; -import transaction from 'app/redux/Transaction' -import user from 'app/redux/User'; -import tt from 'counterpart'; -import reactForm from 'app/utils/ReactForm'; -import {PrivateKey} from 'golos-lib-js/lib/auth/ecc'; -import LoadingIndicator from 'app/components/elements/LoadingIndicator'; -import CopyToClipboard from 'react-copy-to-clipboard'; -import Icon from 'app/components/elements/Icon'; -import { authRegisterUrl, } from 'app/utils/AuthApiClient'; - -class CreateInvite extends Component { - static propTypes = { - // HTML - account: PropTypes.object.isRequired, - // Redux - isMyAccount: PropTypes.bool.isRequired, - accountName: PropTypes.string.isRequired, - } - - constructor(props) { - super() - this.shouldComponentUpdate = shouldComponentUpdate(this, 'CreateInvite') - this.state = { - errorMessage: '', - successMessage: '', - } - this.initForm(props) - } - - componentDidMount() { - this.generateKeys() - this.setDefaultAmount(); - } - - componentDidUpdate(prevProps) { - if (this.props.min_invite_balance !== prevProps.min_invite_balance) { - this.setDefaultAmount(); - } - } - - setDefaultAmount = () => { - const { min_invite_balance } = this.props; - const { amount } = this.state; - if (min_invite_balance && !amount.value) { - const val = (parseInt(min_invite_balance) + 1).toString() - amount.props.onChange(val) - } - } - - updatePrivateKey = (pk) => { - this.state.private_key.props.onChange(pk); - - const register_link = authRegisterUrl() + `?invite=${pk}`; - this.state.register_link.props.onChange(register_link); - } - - generateKeys = () => { - const pk = PrivateKey.fromSeed(Math.random().toString()); - this.updatePrivateKey(pk.toString()) - this.setState({ - createdInvite: '', - }); - } - - initForm(props) { - const insufficientFunds = (amount) => { - const balanceValue = props.account.get('balance') - if(!balanceValue) return false - return parseFloat(amount) > parseFloat(balanceValue.split(' ')[0]) - } - - const meetsMinimum = (amount) => { - const minValue = this.props.min_invite_balance - if (!minValue) return false - return parseFloat(amount) < parseFloat(minValue.split(' ')[0]) - } - - const validateSecret = (secret) => { - try { - PrivateKey.fromWif(secret); - return null; - } catch (e) { - return tt('invites_jsx.claim_wrong_secret_format'); - } - }; - - const fields = ['private_key', 'register_link', 'amount', 'is_referral:checked'] - reactForm({ - name: 'invite', - instance: this, fields, - initialValues: {}, - validation: values => ({ - private_key: - ! values.private_key ? tt('g.required') : validateSecret(values.private_key), - register_link: - ! values.register_link ? tt('g.required') : null, - amount: - ! parseFloat(values.amount) || /^0$/.test(values.amount) ? tt('g.required') : - insufficientFunds(values.amount) ? tt('transfer_jsx.insufficient_funds') : - meetsMinimum(values.amount) ? tt('invites_jsx.meet_minimum') : - countDecimals(values.amount) > 3 ? tt('transfer_jsx.use_only_3_digits_of_precison') : - null, - is_referral: null, - }) - }) - this.handleSubmitForm = - this.state.invite.handleSubmit(args => this.handleSubmit(args)) - } - - showQrPriv = e => { - this.props.showQRKey({type: 'Invite', text: this.state.private_key.value, isPrivate: true}); - } - - showQrRegLink = e => { - this.props.showQRKey({type: 'Invite', text: this.state.register_link.value, title: tt('invites_jsx.register_link')}); - } - - balanceValue() { - const {account} = this.props - return formatAsset(account.get('balance'), true, false, '') - } - - assetBalanceClick = e => { - e.preventDefault() - // Convert '9 GOLOS' to 9 - this.state.amount.props.onChange(this.balanceValue().split(' ')[0]) - } - - onChangePrivateKey = (e) => { - const {value} = e.target - let pk = value.trim() - this.updatePrivateKey(pk) - } - - onChangeAmount = (e) => { - const {value} = e.target - this.state.amount.props.onChange(formatAmount(value)) - } - - onChangeIsReferral = (e) => { - this.state.is_referral.props.onChange(e.target.checked) - } - - handleSubmit = ({updateInitialValues}) => { - const {createInvite, accountName} = this.props - const {private_key, amount, is_referral} = this.state - this.setState({loading: true}); - const public_key = PrivateKey.fromWif(private_key.value).toPublicKey().toString(); - createInvite({public_key, amount, is_referral, accountName, - errorCallback: (e) => { - if (e === 'Canceled') { - this.setState({ - loading: false, - errorMessage: '' - }) - } else { - console.log('createInvite ERROR', e) - this.setState({ - loading: false, - errorMessage: tt('g.server_returned_error') - }) - } - }, - successCallback: () => { - this.setState({ - loading: false, - errorMessage: '', - successMessage: tt('invites_jsx.success'), - createdInvite: public_key, - }) - // remove successMessage after a while - setTimeout(() => this.setState({successMessage: ''}), 8000) - }}) - } - - render() { - const {props: {account, isMyAccount, cprops, min_invite_balance}} = this - const {private_key, register_link, amount, is_referral, loading, successMessage, errorMessage, createdInvite} = this.state - const {submitting, valid} = this.state.invite - - let publicKeyLink = null; - if (createdInvite) { - publicKeyLink = `https://gapi.golos.today/api/database_api/get_invite?invite_key=${createdInvite}`; - publicKeyLink=({tt('invites_jsx.public_key_can_be_checked') + ' '} -
{tt('g.here')} - - ); - } - - return (
-
-
-
- {tt('invites_jsx.create_invite_info')} {tt('g.more_hint')} -
-
-
- -
-
-

{tt('invites_jsx.create_invite')}

-
-
- -
-
- - {tt('invites_jsx.private_key')} -
-
- -
- this.onChangePrivateKey(e)} - /> - - - -
- {private_key.touched && private_key.blur && private_key.error && -
{private_key.error} 
- } -
-
- -
-
- {tt('invites_jsx.register_link')} -
-
- -
- - - - -
-
-
- -
-
- {tt('g.amount')} ({min_invite_balance ? formatAsset(min_invite_balance, true, false, '') : '0 GOLOS'}{' ' + tt('g.or_more')}) -
- this.onChangeAmount(e)}/> -
-
- -
- {(amount.touched && amount.error) ? -
- {amount.touched && amount.error && amount.error}  -
: null} -
-
- -
-
-
- -
- {is_referral.touched && is_referral.blur && is_referral.error && -
{is_referral.error} 
- } -
-
- - {publicKeyLink ? (
-
- {publicKeyLink} -
-
) : null} - -
-
- {loading &&
} - {!loading && } - {' '}{ - errorMessage - ? {errorMessage} - : successMessage - ? {successMessage} - : null - } -
-
-
-
-
-
-
-
-
) - } -} -const AssetBalance = ({onClick, balanceValue}) => - {tt('transfer_jsx.balance') + ": " + balanceValue} - -export default connect( - (state, ownProps) => { - const {account} = ownProps - const accountName = account.get('name') - const current = state.user.get('current') - const username = current && current.get('username') - const isMyAccount = username === accountName - const cprops = state.global.get('cprops'); - const min_invite_balance = cprops && cprops.get('min_invite_balance') - return {...ownProps, isMyAccount, accountName, min_invite_balance} - }, - dispatch => ({ - createInvite: ({ - public_key, amount, is_referral, accountName, successCallback, errorCallback - }) => { - let operation = { - creator: accountName, - balance: parseFloat(amount.value, 10).toFixed(3) + ' GOLOS', - invite_key: public_key - } - if (is_referral.value) { - operation.extensions = [[0, { - is_referral: true, - }]]; - } - - const success = () => { - dispatch(user.actions.getAccount()) - successCallback() - } - - dispatch(transaction.actions.broadcastOperation({ - type: 'invite', - accountName, - operation, - successCallback: success, - errorCallback - })) - }, - - showQRKey: ({type, isPrivate, text, title}) => { - dispatch(g.actions.showDialog({name: "qr_key", params: {type, isPrivate, text, title}})); - } - }) -)(CreateInvite) diff --git a/app/components/elements/Invites.jsx b/app/components/elements/Invites.jsx deleted file mode 100644 index 7b93a2370..000000000 --- a/app/components/elements/Invites.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, {Component} from 'react' -import PropTypes from 'prop-types' -import shouldComponentUpdate from 'app/utils/shouldComponentUpdate' -import g from 'app/redux/GlobalReducer' -import {connect} from 'react-redux'; -import CreateInvite from 'app/components/elements/CreateInvite' -import ClaimInvite from 'app/components/elements/ClaimInvite' - -class Invites extends Component { - static propTypes = { - // HTML - account: PropTypes.object.isRequired, - } - - constructor() { - super() - this.shouldComponentUpdate = shouldComponentUpdate(this, 'Invites') - } - - render() { - const {account} = this.props - - return (
- - -
) - } -} - -export default connect( - (state, ownProps) => { - return {...ownProps} - }, - dispatch => ({ - }) -)(Invites) diff --git a/app/components/elements/WitnessSettings.jsx b/app/components/elements/WitnessSettings.jsx deleted file mode 100644 index 6237dafea..000000000 --- a/app/components/elements/WitnessSettings.jsx +++ /dev/null @@ -1,191 +0,0 @@ -import React from 'react'; -import {connect} from 'react-redux'; -import transaction from 'app/redux/Transaction' -import reactForm from 'app/utils/ReactForm'; -import LoadingIndicator from 'app/components/elements/LoadingIndicator'; -import tt from 'counterpart'; -import { getMetadataReliably } from 'app/utils/NormalizeProfile'; -import g from 'app/redux/GlobalReducer'; - -class WitnessSettings extends React.Component { - - constructor(props) { - super(); - this.initForm(props); - //this.shouldComponentUpdate = shouldComponentUpdate(this, 'WitnessSettings'); - } - - initForm(props) { - reactForm({ - instance: this, - name: 'witnessSettings', - fields: ['url', 'signing_key'], - initialValues: { - url: props.witness_obj.get('url'), - signing_key: props.witness_obj.get('signing_key'), - ...props.witness}, - validation: values => ({ - url: values.url && !/^https?:\/\//.test(values.url) ? tt('settings_jsx.invalid_url') : null, - }) - }); - this.handleSubmitForm = - this.state.witnessSettings.handleSubmit(args => this.handleSubmit(args)); - } - - handleSubmit = ({updateInitialValues}) => { - const {url, signing_key} = this.state - - const {account, updateWitness} = this.props - this.setState({loading: true}) - updateWitness({ - owner: account.name, - url: url.value, - fee: '1.000 GOLOS', - block_signing_key: signing_key.value, - props: { - account_creation_fee: this.props.witness_obj.get('props').get('account_creation_fee'), - maximum_block_size: this.props.witness_obj.get('props').get('maximum_block_size'), - sbd_interest_rate: this.props.witness_obj.get('props').get('sbd_interest_rate') - }, - errorCallback: (e) => { - if (e === 'Canceled') { - this.setState({ - loading: false, - errorMessage: '' - }) - } else { - console.log('updateWitness ERROR', e) - this.setState({ - loading: false, - changed: false, - errorMessage: tt('g.server_returned_error') - }) - } - }, - successCallback: () => { - this.setState({ - loading: false, - changed: false, - errorMessage: '', - successMessage: tt('g.saved') + '!', - }) - // remove successMessage after a while - setTimeout(() => this.setState({successMessage: ''}), 4000) - updateInitialValues() - } - }); - } - - clearSigningKey = (e) => { - e.preventDefault(); - this.state.signing_key.props.onChange('GLS1111111111111111111111111111111114T1Anm'); - } - - render() { - const { - props: {account}, - } = this; - - const {state} = this - - const {submitting, valid, touched} = this.state.witnessSettings - const disabled = state.loading || submitting || !valid || !touched - - const {url, signing_key} = this.state - - const showFeedsNodes = (e) => { - e.preventDefault() - const name = account.name - this.props.feedsNodes(name) - } - - return (
-
-
-

Делегат {this.props.account.name}

-    - - Фиды & Ноды   - {state.loading &&
} - {!state.loading && } - {' '}{ - state.errorMessage - ? {state.errorMessage} - : state.successMessage - ? {state.successMessage} - : null - } -
- - - - - - - - - - - -
- -   -
- - X -
-
{signing_key.touched && signing_key.error}
-
-
); - } - componentDidMount() { - } -} - -export default connect( - // mapStateToProps - (state, props) => { - const { account } = props; - let metaData = account ? getMetadataReliably(account.json_metadata) : {} - const witness = metaData && metaData.witness ? metaData.witness : {} - - return { - metaData, - witness, - witness_obj: state.global.getIn(['witnesses', account.name]) - }; - }, - // mapDispatchToProps - dispatch => ({ - updateAccount: ({successCallback, errorCallback, ...operation}) => { - const success = () => { - //dispatch(user.actions.getAccount()) - successCallback() - } - - const options = {type: 'account_metadata', operation, successCallback: success, errorCallback} - dispatch(transaction.actions.broadcastOperation(options)) - }, - publishFeed: ({successCallback, errorCallback, ...operation}) => { - const success = () => { - //dispatch(user.actions.getAccount()) - successCallback() - } - - const options = {type: 'feed_publish', operation, successCallback: success, errorCallback} - dispatch(transaction.actions.broadcastOperation(options)) - }, - updateWitness: ({successCallback, errorCallback, ...operation}) => { - const success = () => { - //dispatch(user.actions.getAccount()) - successCallback() - } - - const options = {type: 'witness_update', operation, successCallback: success, errorCallback} - dispatch(transaction.actions.broadcastOperation(options)) - }, - feedsNodes: (username) => { - dispatch(g.actions.showDialog({name: 'feeds_nodes', params: {username}})) - } - }) -)(WitnessSettings) diff --git a/app/components/elements/WitnessSettings.scss b/app/components/elements/WitnessSettings.scss deleted file mode 100644 index 86aa50171..000000000 --- a/app/components/elements/WitnessSettings.scss +++ /dev/null @@ -1,36 +0,0 @@ -.inline { -display: inline-block; -} - -input[name=url2] { -width: 400px; -display: inline-block; -} - -input[name=url2]:not(:hover):not(:focus) { -border: none; -transition: none; --webkit-transition: none; -box-shadow: none; --webkit-box-shadow: none; -} - -.clear-table td { - padding: 0; -} - -.clear-table tr { - background-color: inherit !important; -} - -.clear-table tbody { - border: none; -} - -.no-margin-bottom { - margin-bottom: 0rem !important; -} - -.margin-bottom { - margin-bottom: 1rem !important; -} \ No newline at end of file diff --git a/app/components/modules/Dialogs.jsx b/app/components/modules/Dialogs.jsx index 60adf5fdf..a36a3db25 100644 --- a/app/components/modules/Dialogs.jsx +++ b/app/components/modules/Dialogs.jsx @@ -7,14 +7,12 @@ import g from 'app/redux/GlobalReducer'; import {Map, List} from 'immutable'; import shouldComponentUpdate from 'app/utils/shouldComponentUpdate'; import QrReader from 'app/components/elements/QrReader'; -import ConvertToSteem from 'app/components/elements/ConvertToSteem'; import SuggestPassword from 'app/components/elements/SuggestPassword'; import ChangePassword from 'app/components/elements/ChangePassword'; import CheckLoginOwner from 'app/components/elements/CheckLoginOwner'; import QrKeyView from 'app/components/elements/QrKeyView'; import PromotePost from 'app/components/modules/PromotePost'; import ExplorePost from 'app/components/modules/ExplorePost'; -import FeedsNodes from 'app/components/modules/FeedsNodes'; class Dialogs extends React.Component { static propTypes = { @@ -48,12 +46,6 @@ class Dialogs extends React.Component { : - k === 'convertToSteem' ? - - - - - : k === 'suggestPassword' ? @@ -83,12 +75,6 @@ class Dialogs extends React.Component { - : - k === 'feeds_nodes' ? - - - - : null return cmp ? r.push(cmp) : r diff --git a/app/components/modules/Header.jsx b/app/components/modules/Header.jsx index 1eb944f6f..62e0887d7 100644 --- a/app/components/modules/Header.jsx +++ b/app/components/modules/Header.jsx @@ -144,8 +144,6 @@ class Header extends React.Component { if(route.params[1] === "posts" || route.params[1] === "comments"){ page_title = tt('header_jsx.comments_by') + " " + user_title; } - } else if (route.page === 'ConvertAssetsLoader') { - page_title = tt('g.convert_assets') } else { page_name = ''; //page_title = route.page.replace( /([a-z])([A-Z])/g, '$1 $2' ).toLowerCase(); } diff --git a/app/components/modules/PromotePost.jsx b/app/components/modules/PromotePost.jsx index 1c6ff39e3..f466ad81b 100644 --- a/app/components/modules/PromotePost.jsx +++ b/app/components/modules/PromotePost.jsx @@ -160,6 +160,7 @@ export default connect( dispatch(transaction.actions.broadcastOperation({ type: 'transfer', operation, + username, successCallback, errorCallback })) diff --git a/app/components/modules/WitnessProps.jsx b/app/components/modules/WitnessProps.jsx deleted file mode 100644 index 331a8ae34..000000000 --- a/app/components/modules/WitnessProps.jsx +++ /dev/null @@ -1,263 +0,0 @@ -import React from 'react'; -import {connect} from 'react-redux'; -import transaction from 'app/redux/Transaction' -import reactForm from 'app/utils/ReactForm'; -import LoadingIndicator from 'app/components/elements/LoadingIndicator'; -import tt from 'counterpart'; -import WitnessSettings from 'app/components/elements/WitnessSettings'; - -class WitnessProps extends React.Component { - - constructor(props) { - super(); - this.initForm(props); - //this.shouldComponentUpdate = shouldComponentUpdate(this, 'WitnessProps'); - } - - wprops_19 = [ - [ - ['account_creation_fee', 'golos'], - ['create_account_min_golos_fee', 'golos'], - ['create_account_min_delegation', 'golos'], - ['create_account_delegation_time', 'raw'], - ], - [ - ['max_referral_interest_rate'], - ['max_referral_term_sec', 'time'], - ['min_referral_break_fee', 'golos'], - ['max_referral_break_fee', 'golos'], - ], - [ - ['maximum_block_size', 'raw'], - ['worker_emission_percent'], - ['vesting_of_remain_percent'], - ], - [ - ['sbd_interest_rate'], - ['convert_fee_percent'], - ['sbd_debt_convert_rate'], - ], - [ - ['asset_creation_fee', 'gbg'], - ['min_delegation', 'golos'], - ['max_delegated_vesting_interest_rate'], - ], - [ - ['posts_window', 'raw'], - ['posts_per_window', 'raw'], - ], - [ - ['comments_window', 'raw'], - ['comments_per_window', 'raw'], - ], - [ - ['votes_window', 'raw'], - ['votes_per_window', 'raw'], - ['vote_regeneration_per_day', 'raw'], - ], - [ - ['negrep_posting_window', 'dropped', 0], - ['negrep_posting_per_window', 'dropped', 0], - ['custom_ops_bandwidth_multiplier', 'raw'], - ['unwanted_operation_cost', 'golos'], - ['unlimit_operation_cost', 'golos'], - ], - [ - ['min_golos_power_to_curate', 'golos'], - ['curation_reward_curve', ['bounded','linear','square_root']], - ['min_curation_percent'], - ['max_curation_percent'], - ], - [ - ['min_invite_balance', 'golos'], - ['invite_transfer_interval_sec', 'time'], - ['worker_request_creation_fee', 'gbg'], - ['worker_request_approve_min_percent'], - ], - [ - ['witness_skipping_reset_time', 'time'], - ['witness_idleness_time', 'time'], - ['account_idleness_time', 'time'], - ['claim_idleness_time', 'dropped', 0], - ], - [ - ['worker_reward_percent', 'dropped', 0], - ['witness_reward_percent', 'dropped', 0], - ['vesting_reward_percent', 'dropped', 0], - ], - [ - ['auction_window_size', 'dropped', 0], - ['allow_distribute_auction_reward', 'dropped', 'true'], - ['allow_return_auction_reward_to_fund', 'dropped', 'true'], - ], - ]; - - wprops_22 = [ - ]; - - initForm(props) { - this.wprops = [...this.wprops_19, ...this.wprops_22]; - this.wp_flat = this.wprops.flat(); - this.prop_names = this.wp_flat.map(p => p[0]); - reactForm({ - instance: this, - name: 'witnessProps', - fields: this.prop_names, - initialValues: props.witness_obj.toJS().props, - validation: values => ({ - }) - }); - this.handleSubmitForm = - this.state.witnessProps.handleSubmit(args => this.handleSubmit(args)); - } - - handleSubmit = ({updateInitialValues}) => { - const {account, updateChainProperties} = this.props; - this.setState({loading: true}); - - let props = {}; - for (let prop of this.wp_flat) { - if (prop.length === 1 || prop[1] == 'raw' || prop[1] == 'time') { - props[prop[0]] = parseInt(this.state[prop[0]].value); - } else if (prop[1] === 'dropped') { - props[prop[0]] = prop[2]; - } else { - props[prop[0]] = this.state[prop[0]].value; - } - } - if (props.curation_reward_curve == 'bounded') { - props.curation_reward_curve = 0; - } else if (props.curation_reward_curve == 'linear') { - props.curation_reward_curve = 1; - } else if (props.curation_reward_curve == 'square_root') { - props.curation_reward_curve = 2; - } - props.create_account_delegation_time = parseInt(props.create_account_delegation_time); - props.custom_ops_bandwidth_multiplier = parseInt(props.custom_ops_bandwidth_multiplier); - props.maximum_block_size = parseInt(props.maximum_block_size); - props.sbd_interest_rate = parseInt(props.sbd_interest_rate); - props.sbd_debt_convert_rate = parseInt(props.sbd_debt_convert_rate); - props.max_delegated_vesting_interest_rate = parseInt(props.max_delegated_vesting_interest_rate); - props.max_referral_term_sec = parseInt(props.max_referral_term_sec); - props.max_referral_interest_rate = parseInt(props.max_referral_interest_rate); - props.posts_window = parseInt(props.posts_window); - props.comments_window = parseInt(props.comments_window); - props.posts_per_window = parseInt(props.posts_per_window); - props.comments_per_window = parseInt(props.comments_per_window); - updateChainProperties({ - owner: account.name, - props: [7, props], - errorCallback: (e) => { - if (e === 'Canceled') { - this.setState({ - loading: false, - errorMessage: '' - }) - } else { - console.error('updateChainProperties ERROR', e) - this.setState({ - loading: false, - changed: false, - errorMessage: tt('g.server_returned_error') - }) - } - }, - successCallback: () => { - this.setState({ - loading: false, - changed: false, - errorMessage: '', - successMessage: tt('g.saved') + '!', - }) - // remove successMessage after a while - setTimeout(() => this.setState({successMessage: ''}), 4000) - updateInitialValues() - } - }); - } - - render() { - const { - props: {current_user, json_metadata}, - } = this; - //const username = current_user ? current_user.get('username') : null - - const {state} = this - - const {submitting, valid, touched} = this.state.witnessProps; - const disabled = state.loading || submitting || !valid || !touched - - let groups = this.wprops.map((wp) => { - let fields = wp.map((f) => { - const field = this.state[f[0]]; - - let input = null; - if (f[1] === 'bool') { - input = - } else if (f[1] === 'raw') { - input = - } else if (f[1] === 'dropped') { - return null; - } else { - input = - } - - return ( - -
{field.touched && field.error}
- ); - }); - return ({fields}); - }); - - return (
- -
-
-

Параметры сети

   - - {state.loading &&
} - {!state.loading && } - {' '}{ - state.errorMessage - ? {state.errorMessage} - : state.successMessage - ? {state.successMessage} - : null - } -
- - {groups} -
- -
-
); - } - componentDidMount() { - } -} - -export default connect( - // mapStateToProps - (state, props) => { - const { account } = props; - - return { - witness_obj: state.global.getIn(['witnesses', account.name]) - }; - }, - // mapDispatchToProps - dispatch => ({ - updateChainProperties: ({successCallback, errorCallback, ...operation}) => { - const success = () => { - //dispatch(user.actions.getAccount()) - successCallback() - } - - const options = {type: 'chain_properties_update', operation, successCallback: success, errorCallback} - dispatch(transaction.actions.broadcastOperation(options)) - } - }) -)(WitnessProps) diff --git a/app/components/modules/WitnessProps.scss b/app/components/modules/WitnessProps.scss deleted file mode 100644 index 7ac46ef15..000000000 --- a/app/components/modules/WitnessProps.scss +++ /dev/null @@ -1,15 +0,0 @@ -.WitnessPropsTable label { - text-transform: none; -} - -.WitnessPropsTable input { - height: 1.5rem; -} - -.WitnessPropsTable input[type="checkbox"] { - display: block; -} - -.WitnessPropsTable td { - padding: 0 5px; -} diff --git a/app/components/pages/UserProfile.scss b/app/components/pages/UserProfile.scss index 34ca19397..d8fdbf8d9 100644 --- a/app/components/pages/UserProfile.scss +++ b/app/components/pages/UserProfile.scss @@ -262,81 +262,6 @@ } } -.CreateInvite__copy-button { - cursor: pointer; - - .Icon { - width: 2.12rem; - height: 2.12rem; - - & > svg { - width: 2.12rem; - height: 2.12rem; - vertical-align: top; - } - } -} - -.Assets__header { - display: inline-block; -} - -.Assets__disabled { - color: rgb(150, 150, 150); - cursor: default; -} - -.Assets__info { - font-size: 90%; -} - -.Assets__center { - text-align: center; -} - -.Assets__cost { - font-weight: bold !important; -} - -.Assets__marginTop { - margin-top: 13px; -} - -.Assets__marginTop2 { - margin-top: 5px; -} - -.Assets__marginRight { - margin-right: 6px; -} - -.Assets__marginBottom { - margin-bottom: 3px; -} - -.Assets__noMarginBottom { - margin-bottom: 0px; -} - -.Assets__inlineBtn { - margin-bottom: 0px; - margin-left: 0.5rem; - margin-right: 0px !important; -} - -.FilledOrders__market-link { - svg { - fill: #0078C4; - } -} - -.FilledOrders__convert-link { - svg { - fill: #0078C4; - transform: rotate(90deg); - } -} - @media screen and (max-width: 39.9375em) { .UserProfile__menu-item { diff --git a/app/locales/en.json b/app/locales/en.json index 4589cd02b..09b0e3916 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -882,17 +882,6 @@ "not_desc": "Exchange takes more time than usually. Maybe older will proceed in few days. If no, or if you cannot wait, you can ", "price_warning": "This price is above than recommended one. Continue?" }, - "converttosteem_jsx": { - "title_FROM_TO": "Convert %(FROM)s to %(TO)s", - "confirm_title": "Confirm Convert", - "this_will_take_days": "This action will take place 3.5 days from now and can not be canceled. These tokens will immediately become unavailable", - "tokens_will_be_unavailable": "This action will take place 3.5 days from now and can not be canceled. These tokens will immediately become unavailable", - "this_will_take_days_hint": "This is a price feed conversion. The 3.5 day delay is necessary to prevent abuse from gaming the price feed average", - "fee": "Fee will be ", - "no_fee": "No fee.", - "too_low_amount": "Too low amount.", - "you_will_receive": "You will receive " - }, "time_versions_jsx": { "rev": "rev.", "version_NUM": "(rev. %(NUM)s)" diff --git a/app/locales/ru-RU.json b/app/locales/ru-RU.json index ebfc51743..3670ce8bf 100644 --- a/app/locales/ru-RU.json +++ b/app/locales/ru-RU.json @@ -64,17 +64,6 @@ "ok": "Да", "confirm": "Подтвердить" }, - "converttosteem_jsx": { - "title_FROM_TO": "Конвертация %(FROM)s в %(TO)s", - "confirm_title": "Подтвердите конвертацию", - "this_will_take_days": "Конвертация будет выполняться в течение 3.5 дней.", - "tokens_will_be_unavailable": "Её нельзя отменить и после запуска конвертируемые токены станут недоступны на балансе.", - "this_will_take_days_hint": "Отсрочка в 3.5 дня необходима для снижения злоупотреблений спекуляцией, конвертация пройдёт по средней ценовой котировке.", - "fee": "Комиссия составит ", - "no_fee": "Комиссии нет.", - "too_low_amount": "Слишком маленькая сумма.", - "you_will_receive": "Вы получите " - }, "do_not_bother": { "title": "Не беспокоить", "desc": "Пользователи с репутацией ниже 65 не смогут писать вам сообщения, отправлять переводы и донаты, только комментарии за отдельную плату", diff --git a/app/redux/AuthSaga.js b/app/redux/AuthSaga.js index acd820968..3353a6c33 100644 --- a/app/redux/AuthSaga.js +++ b/app/redux/AuthSaga.js @@ -3,10 +3,7 @@ import {Set, Map, fromJS, List} from 'immutable' import user from 'app/redux/User' import {getAccount} from 'app/redux/SagaShared' import {PrivateKey} from 'golos-lib-js/lib/auth/ecc'; -import {api} from 'golos-lib-js'; - -// operations that require only posting authority -const postingOps = Set(`vote, comment, delete_comment, custom_json, account_metadata, donate, worker_request_vote, account_setup`.trim().split(/,\s*/)) +import { broadcast, api } from 'golos-lib-js'; export function* authWatches() { yield fork(watchForAuth) @@ -98,10 +95,10 @@ function pubkeyThreshold({pubkeys, authority}) { export function* findSigningKey({opType, username, password}) { let authTypes - if (postingOps.has(opType)) { + const opInfo = broadcast._operations[opType] + if (opInfo && opInfo.roles[0] === 'posting') { authTypes = 'posting, active' - } - else { + } else { authTypes = 'active, owner' } authTypes = authTypes.split(', ') diff --git a/app/redux/TransactionSaga.js b/app/redux/TransactionSaga.js index 7b2287f8d..0822a0c5b 100644 --- a/app/redux/TransactionSaga.js +++ b/app/redux/TransactionSaga.js @@ -204,9 +204,12 @@ function* broadcastOperation( payload.keys.push(signingKey) else { if (!password) { + const opInfo = broadcast._operations[type] + let authType = opInfo && opInfo.roles[0] + if (authType === 'posting') authType = '' yield put(user.actions.showLogin({ operation: {type, operation: op, trx, username, successCallback, errorCallback, saveLogin: true}, - loginDefault: { username, authType: (type == 'transfer' || type == 'account_witness_vote' || type == 'account_witness_proxy') ? 'active' : '' } + loginDefault: { username, authType } })) return } diff --git a/app/redux/User.js b/app/redux/User.js index 006d0a1fc..e93493851 100644 --- a/app/redux/User.js +++ b/app/redux/User.js @@ -50,27 +50,6 @@ export default createModule({ { action: 'SAVE_LOGIN_CONFIRM', reducer: (state, {payload}) => state.set('saveLoginConfirm', payload) }, { action: 'SAVE_LOGIN', reducer: (state) => state }, // Use only for low security keys (like posting only keys) { action: 'GET_ACCOUNT', reducer: (state) => state }, - { action: 'REMOVE_HIGH_SECURITY_KEYS', reducer: (state) => { - if(!state.hasIn(['current', 'private_keys'])) return state - let empty = false - state = state.updateIn(['current', 'private_keys'], private_keys => { - if(!private_keys) return null - if(private_keys.has('active_private')) - console.log('removeHighSecurityKeys') - private_keys = private_keys.delete('active_private') - empty = private_keys.size === 0 - return private_keys - }) - if(empty) { - // User logged in with Active key then navigates away from the page - // LOGOUT - return defaultState.merge({logged_out: true}) - } - const username = state.getIn(['current', 'username']) - state = state.setIn(['authority', username, 'active'], 'none') - state = state.setIn(['authority', username, 'owner'], 'none') - return state - }}, { action: 'CHANGE_CURRENCY', reducer: (state, {payload}) => { return state.set('currency', payload)} }, diff --git a/app/redux/UserSaga.js b/app/redux/UserSaga.js index 77ac84061..d81b4e401 100644 --- a/app/redux/UserSaga.js +++ b/app/redux/UserSaga.js @@ -19,7 +19,6 @@ import uploadImageWatch from './UserSaga_UploadImage'; import session from 'app/utils/session' export function* userWatches() { - yield fork(watchRemoveHighSecurityKeys); // keep first to remove keys early when a page change happens yield fork(loginWatch); yield fork(changeAccountWatch) yield fork(saveLoginWatch); @@ -33,8 +32,6 @@ export function* userWatches() { -const highSecurityPages = Array() - function* lookupPreviousOwnerAuthorityWatch() { yield takeLatest('user/lookupPreviousOwnerAuthority', lookupPreviousOwnerAuthority); } @@ -59,10 +56,6 @@ function* watchLoadSavingsWithdraw() { yield takeLatest('user/LOAD_SAVINGS_WITHDRAW', loadSavingsWithdraw); } -export function* watchRemoveHighSecurityKeys() { - yield takeLatest('@@router/LOCATION_CHANGE', removeHighSecurityKeys); -} - function* loadSavingsWithdraw() { const username = yield select(state => state.user.getIn(['current', 'username'])) const to = yield call([api, api.getSavingsWithdrawToAsync], username) @@ -90,16 +83,6 @@ function* getAccountWatch() { yield takeEvery('user/GET_ACCOUNT', getAccountHandler); } -function* removeHighSecurityKeys({payload: {pathname}}) { - const highSecurityPage = highSecurityPages.find(p => p.test(pathname)) != null - // Let the user keep the active key when going from one high security page to another. This helps when - // the user logins into the Wallet then the Permissions tab appears (it was hidden). This keeps them - // from getting logged out when they click on Permissions (which is really bad because that tab - // disappears again). - if(!highSecurityPage) - yield put(user.actions.removeHighSecurityKeys()) -} - /** @arg {object} action.username - Unless a WIF is provided, this is hashed with the password and key_type to create private keys. @arg {object} action.password - Password or WIF private key. A WIF becomes the posting key, a password can create all three @@ -113,11 +96,8 @@ function* usernamePasswordLogin(action) { const username = current.get('username') yield fork(loadFollows, "getFollowingAsync", username, 'blog') if (process.env.BROWSER) { - //const notification_channel_created = yield select(state => state.user.get('notification_channel_created')) - //if (!notification_channel_created) { - const { onUserLogin } = PushNotificationSaga; - yield call(onUserLogin, { username }); - //} + const { onUserLogin } = PushNotificationSaga + yield call(onUserLogin, { username }) } } } @@ -129,7 +109,7 @@ function* usernamePasswordLogin(action) { const clean = (value) => value == null || value === '' || /null|undefined/.test(value) ? undefined : value function* usernamePasswordLogin2({payload: {username, password, saveLogin, - operationType /*high security*/, afterLoginRedirectToWelcome + operationType, highSecurityLogin, afterLoginRedirectToWelcome }}) { // login, using saved password let autopost, memoWif, login_owner_pubkey, login_wif_owner_pubkey @@ -163,12 +143,6 @@ function* usernamePasswordLogin2({payload: {username, password, saveLogin, [username, userProvidedRole] = username.split('/') } - const pathname = yield select(state => state.global.get('pathname')) - const highSecurityLogin = - // /owner|active/.test(userProvidedRole) || - // isHighSecurityOperations.indexOf(operationType) !== -1 || - highSecurityPages.find(p => p.test(pathname)) != null - const isRole = (role, fn) => (!userProvidedRole || role === userProvidedRole ? fn() : undefined) let account = yield call(getAccount, username) @@ -209,7 +183,7 @@ function* usernamePasswordLogin2({payload: {username, password, saveLogin, let authority = yield select(state => state.user.getIn(['authority', username])) const hasActiveAuth = authority.get('active') === 'full' // Forbid loging in with active key - if(!operationType) { + if(!operationType && !highSecurityLogin) { const accountName = account.get('name') authority = authority.set('active', 'none') yield put(user.actions.setAuthority({accountName, auth: authority})) @@ -241,6 +215,8 @@ function* usernamePasswordLogin2({payload: {username, password, saveLogin, if (authority.get('posting') !== 'full') private_keys = private_keys.remove('posting_private') + const pathname = yield select(state => state.global.get('pathname')) + if((!highSecurityLogin || authority.get('active') !== 'full') && !pathname.endsWith('/permissions')) private_keys = private_keys.remove('active_private') diff --git a/package.json b/package.json index f5898f7bd..8095cde9b 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "foundation-sites": "^6.4.3", "fs-extra": "^10.0.1", "git-rev-sync": "^1.12.0", - "golos-lib-js": "^0.9.47", + "golos-lib-js": "^0.9.48", "history": "^2.0.0-rc2", "immutable": "^3.8.2", "intl": "^1.2.5", diff --git a/yarn.lock b/yarn.lock index 074f9a5d5..72eb9278a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4619,9 +4619,9 @@ core-js@^3.0.4, core-js@^3.19.0, core-js@^3.19.1, core-js@^3.6.5, core-js@^3.8.2 integrity sha512-Tnc7E9iKd/b/ff7GFbhwPVzJzPztGrChB8X8GLqoYGdEOG8IpLnK1xPyo3ZoO3HsK6TodJS58VGPOxA+hLHQMg== core-js@^3.17.3: - version "3.25.5" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.5.tgz#e86f651a2ca8a0237a5f064c2fe56cef89646e27" - integrity sha512-nbm6eZSjm+ZuBQxCUPQKQCoUEfFOXjUZ8dTTyikyKaWrTYmAVbykQfwsKE5dBK88u3QCkCrzsx/PPlKfhsvgpw== + version "3.26.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.26.0.tgz#a516db0ed0811be10eac5d94f3b8463d03faccfe" + integrity sha512-+DkDrhoR4Y0PxDz6rurahuB+I45OsEUv8E1maPTB6OuHRohMMcznBq9TMpdpDMm/hUPob/mJJS3PqgbHpMTQgw== core-js@^3.6.0: version "3.12.1" @@ -7263,10 +7263,10 @@ globule@^1.0.0: lodash "~4.17.10" minimatch "~3.0.2" -golos-lib-js@^0.9.47: - version "0.9.47" - resolved "https://registry.yarnpkg.com/golos-lib-js/-/golos-lib-js-0.9.47.tgz#5a6868dadf230c4bdd7693a5e3a8d9814d997db7" - integrity sha512-svitIWlx6eNYURFBCm9w85qNRhs3QQ9C1ti60HJb4HNyjD3Ixr2/8Bj7VlhFTicMFzuqMPxQYPeemtrmwMAFtw== +golos-lib-js@^0.9.48: + version "0.9.48" + resolved "https://registry.yarnpkg.com/golos-lib-js/-/golos-lib-js-0.9.48.tgz#d65ea3e852600c9bc5369585d94b042d1d432ad3" + integrity sha512-M7lcKSYuLs89bS508NJiLsJ90FERo9n5smFtspYDc5TdLZPGm2F31LSUgTSNt5wRriHZ7XF+WiBFcfqMcrRF8g== dependencies: abort-controller "^3.0.0" assert "^2.0.0" @@ -15664,9 +15664,9 @@ ws@^4.0.0: safe-buffer "~5.1.0" ws@^8.2.3: - version "8.9.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.9.0.tgz#2a994bb67144be1b53fe2d23c53c028adeb7f45e" - integrity sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg== + version "8.10.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.10.0.tgz#00a28c09dfb76eae4eb45c3b565f771d6951aa51" + integrity sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw== x-xss-protection@1.1.0: version "1.1.0" From d0b876e3c5cb11da72fc5c1967bcb992eeb99501 Mon Sep 17 00:00:00 2001 From: Lex-Ai <12001684+Lex-Ai@users.noreply.github.com> Date: Tue, 1 Nov 2022 15:33:52 +0300 Subject: [PATCH 08/17] minor fix --- app/components/modules/TopRightMenu.jsx | 2 +- app/locales/ru-RU.json | 4 ++-- app/redux/Transaction_Error.js | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/modules/TopRightMenu.jsx b/app/components/modules/TopRightMenu.jsx index 4d67b7142..e07092512 100644 --- a/app/components/modules/TopRightMenu.jsx +++ b/app/components/modules/TopRightMenu.jsx @@ -125,7 +125,7 @@ function TopRightMenu({account, savings_withdraws, price_per_golos, globalprops, } additional_menu.push( { link: '#', onClick: toggleNightmode, icon: 'editor/eye', value: tt('g.night_mode') }, - { link: walletUrl('/market/GOLOS/GBG'), target: walletTarget(), icon: 'trade', value: tt("navigation.market") }, + { link: walletUrl('/market'), target: walletTarget(), icon: 'trade', value: tt("navigation.market") }, { link: '/services', icon: 'new/monitor', value: tt("navigation.services") }, { link: '/search', icon: 'new/search', value: tt("navigation.search") }, { link: walletUrl('/exchanges'), target: walletTarget(), icon: 'editor/coin', value: tt("navigation.buy_sell") }, diff --git a/app/locales/ru-RU.json b/app/locales/ru-RU.json index 3670ce8bf..e0e67f904 100644 --- a/app/locales/ru-RU.json +++ b/app/locales/ru-RU.json @@ -241,7 +241,7 @@ "change_acc": "Сменить аккаунт", "mentions": "Упоминания", "memo": "Ключ заметок", - "messages": "Сообщения", + "messages": "Мессенджер", "modified": " (изменено)", "more": "больше", "more_hint": "Подробнее", @@ -656,7 +656,7 @@ "sign_up": "СОЗДАТЬ АККАУНТ", "welcome": "Добро пожаловать", "feedback": "Обратная связь", - "search": "Поиск по контенту", + "search": "Поиск по блогам", "services": "Сервисы, игры, боты", "wiki": "База знаний (Wiki)", "faq": "Вопросы и ответы", diff --git a/app/redux/Transaction_Error.js b/app/redux/Transaction_Error.js index c8ac7be5a..9419aa4de 100644 --- a/app/redux/Transaction_Error.js +++ b/app/redux/Transaction_Error.js @@ -50,8 +50,8 @@ export default function transactionErrorReducer( ) ) { errorKey = - 'Account requires 10x the account creation fee in Golos Power ' + - '(approximately 300 SP) before it can power down.'; + 'To power down requires 10x the account creation fee in Golos Power ' + + '(~ 1000 GP) before it can power down.'; } else if ( errorStr.includes( 'Account does not have sufficient Golos Power for withdraw.' From 07d4669cbe74335dd3d5cd47b629917af34f7dfb Mon Sep 17 00:00:00 2001 From: Lex-Ai <12001684+Lex-Ai@users.noreply.github.com> Date: Thu, 3 Nov 2022 02:09:12 +0300 Subject: [PATCH 09/17] minor fix --- app/components/all.scss | 1 + app/components/cards/PostFull.jsx | 8 +- app/components/cards/PostFull.scss | 6 +- app/components/cards/PostSummary.scss | 7 +- app/components/elements/donate/Donate.scss | 68 ++++ app/components/modules/TopRightMenu.jsx | 6 +- app/components/pages/Post.jsx | 12 +- app/components/pages/UserProfile.jsx | 23 +- app/locales/en.json | 342 -------------------- app/locales/ru-RU.json | 343 --------------------- 10 files changed, 106 insertions(+), 710 deletions(-) create mode 100644 app/components/elements/donate/Donate.scss diff --git a/app/components/all.scss b/app/components/all.scss index 9332b2c5f..9a498ce24 100644 --- a/app/components/all.scss +++ b/app/components/all.scss @@ -47,6 +47,7 @@ @import "./elements/common/DialogManager/index"; @import "./elements/common/TooltipManager/index"; @import "./elements/market/CMCWidget"; +@import "./elements/donate/Donate"; @import "./elements/postEditor/MarkdownEditor/MarkdownEditor"; @import "./elements/postEditor/MarkdownEditorToolbar/index"; @import "./elements/postEditor/EditorSwitcher/EditorSwitcher"; diff --git a/app/components/cards/PostFull.jsx b/app/components/cards/PostFull.jsx index 26c8bc797..70dec28b2 100644 --- a/app/components/cards/PostFull.jsx +++ b/app/components/cards/PostFull.jsx @@ -48,7 +48,13 @@ function TimeAuthorCategory({ content, authorRepLog10, showTags }) { function TimeAuthorCategoryLarge({ content, authorRepLog10 }) { return ( - + + + + + + {content.views} / +
.eye { + color: $dark-gray; + margin-right: .4rem; + } > .Userpic { margin-top: -4px; float: left; diff --git a/app/components/cards/PostSummary.scss b/app/components/cards/PostSummary.scss index b801a2b98..362c909b5 100644 --- a/app/components/cards/PostSummary.scss +++ b/app/components/cards/PostSummary.scss @@ -12,7 +12,7 @@ ul.PostsList__summaries { @include clearfix; .nsfw-text { - filter: blur(4px); + filter: blur(3px); .PostSummary__footer { margin-top: 0.2rem; .Reblog__button {display: none;} @@ -24,7 +24,7 @@ ul.PostsList__summaries { } .nsfw-img { - filter: blur(7px); + filter: blur(5px); } .nsfw_post { @@ -64,7 +64,7 @@ ul.PostsList__summaries { .PostSummary__replies { font-weight: bold; color: red; - padding-left: 5px; + padding-left: 10px; font-size: 80%; text-decoration: none; } @@ -191,6 +191,7 @@ ul.PostsList__summaries { font-size: 90%; span { margin-left: 0.5rem; + margin-bottom: 0.5rem; } &:hover { path { diff --git a/app/components/elements/donate/Donate.scss b/app/components/elements/donate/Donate.scss new file mode 100644 index 000000000..70899b537 --- /dev/null +++ b/app/components/elements/donate/Donate.scss @@ -0,0 +1,68 @@ +.TipBalance { + display: inline-block; + margin-left: 5px; + vertical-align: middle; + padding-left: 1em; + padding-right: 1em; + padding-bottom: 0.75rem; + text-align: center; + width: 155px; + font-size: 95%; + + .VerticalMenu { + width: 155px; + min-width: 155px; + } + + &.mini { + font-size: 80%; + } + + &.micro { + font-size: 70%; + } +} + +.PresetSelector { + border-radius: 0px; + margin-left: -1px !important; + margin-right: 0px !important; + + border: 1px solid #0078C4 !important; + color: #0078C4 !important; + font-size: 1em; + min-width: 55px; +} + +.PresetSelector:nth-child(n+3):nth-last-child(n+3) { +} + +.PresetSelector:first-child { + border-top-left-radius: 10px; + border-bottom-left-radius: 10px; + margin-right: 0px !important; + margin-left: 0px !important; +} + +.PresetSelector:last-child { + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; +} + +.PresetSelector:hover { + border: 1px solid #002080 !important; + color: #002080 !important; + position: relative; + z-index: 10; +} + +.PresetSelector__active { + border: 1px solid #002080 !important; + color: #002080 !important; + position: relative; + z-index: 10; +} + +.PresetSelector__container { + display: inline-block; +} diff --git a/app/components/modules/TopRightMenu.jsx b/app/components/modules/TopRightMenu.jsx index e07092512..3bbd97cc3 100644 --- a/app/components/modules/TopRightMenu.jsx +++ b/app/components/modules/TopRightMenu.jsx @@ -156,12 +156,12 @@ function TopRightMenu({account, savings_withdraws, price_per_golos, globalprops, {link: accountLink, icon: 'new/blogging', value: tt('g.blog')}, {link: repliesLink, icon: 'new/answer', value: tt('g.replies'), addon: }, {link: discussionsLink, icon: 'new/bell', value: tt('g.discussions'), addon: }, - (messagesLink ? - {link: messagesLink, icon: 'new/envelope', value: tt('g.messages'), target: '_blank', addon: } : - null), {link: mentionsLink, icon: 'new/mention', value: tt('g.mentions'), addon: }, {link: donatesLink, target: walletTarget(), icon: 'editor/coin', value: tt('g.rewards'), addon: }, {link: walletLink, target: walletTarget(), icon: 'new/wallet', value: tt('g.wallet'), addon: }, + (messagesLink ? + {link: messagesLink, icon: 'new/envelope', value: tt('g.messages'), target: '_blank', addon: } : + null), {link: settingsLink, icon: 'new/setting', value: tt('g.settings')}, loggedIn ? {link: '#', icon: 'new/logout', onClick: goChangeAccount, value: tt('g.change_acc')} : diff --git a/app/components/pages/Post.jsx b/app/components/pages/Post.jsx index 0ed591dfd..ed348b015 100644 --- a/app/components/pages/Post.jsx +++ b/app/components/pages/Post.jsx @@ -380,13 +380,19 @@ class Post extends React.Component { {subscribed ? tt('post_jsx.unsubscribe') : tt('post_jsx.subscribe_comments')}
- {positiveComments.length ? - (
+
{tt('post_jsx.sort_order')}:   -
) : null} +
{positiveComments} {negativeGroup} + {positiveComments.length ? + (
this.subscribe(e, dis)}> + + + {subscribed ? tt('post_jsx.unsubscribe') : tt('post_jsx.subscribe_comments')} + +
) : null}
diff --git a/app/components/pages/UserProfile.jsx b/app/components/pages/UserProfile.jsx index e208beb8f..ba9660f3f 100644 --- a/app/components/pages/UserProfile.jsx +++ b/app/components/pages/UserProfile.jsx @@ -420,15 +420,12 @@ export default class UserProfile extends React.Component {
{tt('g.blog')} {tt('g.comments')} - {isMyAccount ? - {tt('g.discussions')} - : null} {tt('g.replies')} {isMyAccount && } - {msgsHost() ? - {tt('g.messages')} {isMyAccount && } - : null} + {isMyAccount ? + {tt('g.discussions')} + : null}
- {tt('g.wallet')} {isMyAccount && } + {tt('g.wallet')} {isMyAccount && } - {isMyAccount ? - {tt('navigation.market2')} - : null - } - {isMyAccount ? + {msgsHost() ? + {tt('g.messages')} {isMyAccount && } + : null} + {isMyAccount ? {tt('g.settings')} - : null - } + : null}
diff --git a/app/locales/en.json b/app/locales/en.json index 09b0e3916..ab428316b 100644 --- a/app/locales/en.json +++ b/app/locales/en.json @@ -57,15 +57,6 @@ "mention_comment": "comment ", "you": "you" }, - "filled_orders_jsx": { - "title": "Filled market orders", - "orders_info": "Golos blockchain has its own internal DEX (decentralized exchange), which means it is possible to exchange GOLOS and UIA tokens (user-issued assets to other cryptocurrencies) right here.", - "empty_NAME": "%(NAME)s have no filled orders yet. You can buy or sell tokens in the ", - "empty2": "market", - "exchanged": "Exchange %(AMOUNT)s for %(AMOUNT2)s", - "quick_convert": "Quick Convert", - "open_market": "Go To Market (DEX)" - }, "g": { "golos_fest": "Golos.id News", "APP_NAME": "Golos", @@ -257,15 +248,6 @@ "passwords_do_not_match": "Passwords do not match", "no_topics_by_order_found": "There are no posts in the category %(order)s for the last 7 days...", "you_need_private_password_or_key_not_a_public_key": "You need a private password or key (not a public key)", - "the_rules_of_APP_NAME": { - "one": "The first rule of %(APP_NAME)s is: Do not lose your password.", - "second": "The second rule of %(APP_NAME)s is: Do not lose your password.", - "third": "The third rule of %(APP_NAME)s is: We cannot recover your password.", - "fourth": "The fourth rule: If you can remember the password, it's not secure.", - "fifth": "The fifth rule: Use only randomly-generated passwords.", - "sixth": "The sixth rule: Do not tell anyone your password.", - "seventh": "The seventh rule: Always back up your password." - }, "current_password": "Current Password", "generated_password": "Generated Password", "recover_password": "Restore Access", @@ -352,17 +334,6 @@ "social_network": "Social network", "about_project": "Golos Blogs - social platform and blogger community, developed on the GOLOS blockchain" }, - "exchanges_jsx": { - "title": "Buying and selling tokens", - "temporarily_disabled": "Token deposit/withdraw is temporarily disabled", - "guide_user": "Instructions for the exchange", - "pause": "Tokens input/output is temporarily suspended...", - "total_supply": "Information about the total supply of tokens", - "other_options": "for RUB / UAH / USD and many other options", - "questions": "Any questions", - "community_chat": "You can get answers in the community chat", - "delegate_chat": "and in the delegates chat" - }, "navigation": { "about": "About", "explore": "Explore", @@ -491,42 +462,6 @@ "account_name_is_not_available": "Account name is not available", "prev_posts": "See also" }, - "market_jsx": { - "action": "Action", - "date_created": "Date Created", - "date_trade": "Date Trade", - "last_price": "Last price", - "24h_volume": "24h volume", - "spread": "Spread", - "total": "Total", - "available": "Available", - "lowest_ask": "Lowest ask", - "highest_bid": "Highest bid", - "buy_orders": "Buy Orders", - "sell_orders": "Sell Orders", - "trade_history": "Trade History", - "open_orders": "Open Orders", - "sell_amount_for_atleast": "Sell %(amount_to_sell)s for at least %(min_to_receive)s (%(effectivePrice)s)", - "buy_atleast_amount_for": "Buy at least %(min_to_receive)s for %(amount_to_sell)s (%(effectivePrice)s)", - "price_warning_above": "This price is well above the current market price of %(marketPrice)s, are you sure?", - "price_warning_below": "This price is well below the current market price of %(marketPrice)s, are you sure?", - "order_cancel_confirm": "Cancel order %(order_id)s from %(user)s?", - "order_cancel_confirm_few": "Cancel %(order_cnt)s order(-s) from %(user)s?", - "order_cancel_all_confirm": "Cancel all orders between %(symbol1)s and %(symbol2)s from %(user)s?", - "order_cancelled": "Order %(order_id)s cancelled.", - "higher": "Higher", - "lower": "Lower", - "total_DEBT_TOKEN_SHORT_CURRENCY_SIGN": "Total %(DEBT_TOKEN_SHORT)s)", - "not_exists": " not exists", - "forbids": " is not tradable with ", - "asset_problem_go_home": "Choose another market pair", - "asset_is_overridable": " can revoke tokes from users balances!", - "asset_": "Token issuer ", - "market_fee_percent_": "Trade fee ", - "market_depth_": "market depth", - "market_pair": "Market Pair", - "orders_canceled": "Selected orders successfully cancelled." - }, "user_profile": { "unknown_account": "Unknown Account", "user_hasnt_made_any_posts_yet": "Looks like %(name)s hasn't made any posts yet!", @@ -565,10 +500,6 @@ "other": "%(count)s posts" } }, - "authorrewards_jsx": { - "estimated_author_rewards_last_week": "Estimated author rewards last week", - "author_rewards_history": "Author Rewards History" - }, "plurals": { "reply_count": { "zero": "no replies", @@ -588,15 +519,6 @@ "transfers_are_temporary_disabled": "Transfers are temporary disabled", "history": "History" }, - "curationrewards_jsx": { - "curation_rewards_last_24_hours": "Curation rewards last 24 hours", - "daily_average_curation_rewards": "Daily average curation rewards", - "estimated_curation_rewards_last_week": "Estimated curation rewards last week", - "curation_rewards_last_week": "Curation rewards last week", - "curation_rewards_history": "Curation Rewards History", - "replenish_golos_power": "Replenish your Golos Power", - "replenish_golos_power_AMOUNT": " with %(AMOUNT)s to receive your curaton rewards." - }, "post_jsx": { "now_showing_comments_with_low_ratings": "Now showing comments with low ratings", "sort_order": "Sort Order", @@ -618,15 +540,6 @@ "blocking_fatal_error": "User blocked you, now sending of transfers and donates is forbidden.", "bother_fatal_error": "User wants to not be bothered by accounts with low-reputation, now sending of transfers and donates is forbidden." }, - "key_file": { - "file_title": "Keys Of @", - "file_desc": "There are private keys, which are authorizing your account. Store this file in secure place.", - "password_desc": "Password (use it to log in on Golos): ", - "posting_desc": "Posting Key (also can be used as Golos password): ", - "active_desc": "Active Key: ", - "owner_desc": "Owner Key: ", - "memo_desc": "Memo Key: " - }, "voting_jsx": { "stop_seeing_content_from_this_user": "Stop seeing content from this user", "flagging_post_can_remove_rewards_the_flag_should_be_used_for_the_following": "Flagging a post can remove rewards and make this material less visible. Some common reasons to flag", @@ -656,43 +569,6 @@ "raw_html": "Raw HTML", "please_remove_following_html_elements": "Please remove the following HTML elements from your post: " }, - "witnesses_jsx": { - "witness_info": "Witnesses - are users who are elected by the community vote and are engaged in supporting the nodes that ensure the operation of the blockchain and many other issues related to the development of the project.", - "witness_thread": "witness thread", - "top_witnesses": "Witness Voting", - "what_is_api": "API-nodes have higher demands on server resources, it is through them come the queries to the blockchain for sending/receiving information, they are important for the development of a variety of clients and services...", - "what_is_seed": "SEED-nodes are the basis for data exchange for the functioning of the blockchain, accepting and distributing blocks they unload the network bandwidth, reducing the response for geographically close connections...", - "you_can_vote_for_maximum_of_witnesses": "You can vote for a maximum of 30 witnesses", - "chat_delegates": "Delegates Chat", - "chain_properties": "Сhain properties", - "witness": "Witness", - "witness_0": "You haven't supported any delegates yet", - "witness_1": "witness with a force of", - "witness_2": "witnesses with a force of", - "witness_supported": "You supported", - "witness_addon": "for each of them", - "information": "Information", - "load_more": "Load more", - "approval": "Votes", - "missed_1": "Missed", - "missed_2": "blocks", - "last_block": "Last block", - "price_feed": "Price feed", - "props": "Props", - "reg_fee": "Registration fee", - "apr": "APR", - "block_size": "Block size", - "witness_deactive": "Withness suspended the signature of the blocks", - "no_price_feed": "Withness did not publish the price feed", - "version": "Version", - "if_you_want_to_vote_outside_of_top_enter_account_name": "If you would like to vote for a witness outside of the top 100, enter the account name below to cast a vote", - "set_witness_proxy": "You can also choose a proxy that will vote for witnesses for you. This will reset your current witness selection.", - "witness_set": "You have set a voting proxy. If you would like to reenable manual voting, please clear your proxy.", - "witness_proxy_current": "Your current proxy is", - "witness_proxy_set": "Set proxy", - "witness_proxy_clear": "Clear proxy", - "proxy_update_error": "Your proxy was not updated" - }, "votesandcomments_jsx": { "no_responses_yet_click_to_respond": "No responses yet. Click to respond.", "response_count": { @@ -713,99 +589,6 @@ }, "account_frozen": "Deactivated due inactivity." }, - "assets_jsx": { - "assets_info": "User Issued Assets (tokens) that can be exchanged, traded on an internal exchange, and used in various blockchain services.", - "create_asset": "Creating asset", - "symbol": "Ticker", - "symbol_too_short": "At least 3 characters", - "subsymbol_too_short": "Too short sub-ticker", - "symbol_exists": "Asset already exists", - "top_symbol_not_your": "Top level asset is not your", - "top_symbol_not_exists": "Top level asset not exists", - "max_supply": "Max supply", - "precision": "Decimals", - "allow_fee": "Allow to set trading fee", - "allow_override_transfer": "Allow revoke assets by creator (TIP-balance will not work)", - "allow_danger_note": "Cannot enable which forbidden on asset created!", - "create_btn": "Create asset", - "description": "Asset description URL", - "image_with_text": "Ticker image URL (48x48)", - "my_assets": "My assets", - "all_assets": "All assets", - "load_more": "Show all", - "anti_load_more": "Collapse", - "creator": "Issuer", - "supply_count": "Tokens issued", - "issue_btn": "Issue", - "update_btn": "Edit", - "balance": "main balance", - "tip_balance": "TIP-balance", - "overridable_no_tip": "Revokable tokens not support TIP balance", - "update_asset": "Editing asset", - "fee_percent": "Trade fee (%%)", - "fee_not_allowed": "Fee not allowed when asset created", - "symbols_whitelist": "Tokens to which can trade (one token per line, if empty - all allowed)", - "transfer_asset_btn": "Transfer to another one", - "transfer_asset_btn2": "Transfer", - "transfer_asset": "Transferring asset", - "transfer_new_owner": "To who", - "cannot_transfer_to_oneself": "Cannot transfer to yourself.", - "trade_asset": "Go to trading on the internal exchange", - "mute_asset": "Do not show asset", - "mute_asset_confirm_TICKER": "Confirm muting of %(TICKER)s? You will be able to unmute this asset in Settings of your profile.", - "deposit": "Deposit", - "withdrawal": "Withdrawal" - }, - "asset_edit_deposit_jsx": { - "title": "Deposit Rules", - "to_type_fixed": "Fixed gate address", - "to_type_transfer": "Requesting gate address", - "to_type_api": "Gate address from API", - "to_fixed": "Where to send tokens:", - "to_transfer": "Where to send 0.001 GOLOS in order to request address:", - "to_api": "Template of request URL, to insert current user account:", - "to_api_example": "Example: https://site.ru/get_address/", - "to_api_confirm": "We hope your API supports requests like that: ", - "to_api_description": "Response should be a JSON object.\nIt should have format like in following example:", - "to_api_error_url": "URL should start with http:// or https://", - "to_api_error": "Template should include - a place for name.", - "memo_fixed": "Memo:", - "memo_transfer": "Memo (for 0.001 GOLOS transfer):", - "transfer_title_SYM": "Deposit Of %(SYM)s", - "transfer_desc": "To receive a deposit address, please send 0.001 GOLOS to ", - "transfer_desc_2": " with memo: ", - "waiting": "Transfer of 0.001 GOLOS was sent. Responding transfer with address will acquire in ~5 minutes...", - "timeouted": "Transfer with address is not acquired. Try again or contact the issuer of ", - "api_error": "Cannot get address. Try again later. If problem still occurs, contact the issuer of ", - "api_error_details": "and send the error details:" - }, - "asset_edit_withdrawal_jsx": { - "title": "Withdrawal Rules", - "to": "Where to send tokens: ", - "min_amount": "Min amount: ", - "details": "Additional info: ", - "unavailable": "Service maintenance", - "way_name": "Service name: ", - "way_name_placeholder": "USDT (TRC-20)", - "way_memo": "Memo for transfer: ", - "way_memo_placeholder": "USDT-wallet-address", - "way_prefix": "Memo prefix: ", - "way_prefix_placeholder": "tron:", - "way_add": "Add another method", - "way_name_error": "Specify the name of the network/system, example: USDT (TRC-20), DOGE...", - "way_prefix_error": "Do not prepend memo itself with the prefix.", - "wrong_prefix_end": "Prefix should end with one of these characters: : - _", - "wrong_prefix_start": "Prefix should not start with space.", - "wrong_memo_start": "Memo should not start with space.", - "wrong_memo_end": "Memo should not end with space.", - "transfer_title_SYM": "Withdrawal of %(SYM)s", - "transfer_desc": "Withdrawal of tokens.", - "transfer_by": "by", - "transfer_way": "Where", - "transfer_no_memo": "To withdraw funds please fill the memo", - "no_way_error": "If you set at leas one of these fields, you should also set at least 1 withdrawal method or Details field.", - "no_to_error": "If you set withdraw via transfer with memo, please also set, to which account to transfer." - }, "invites_jsx": { "create_invite": "Create new invite check", "create_invite_info": "Cheques (invite codes) are a universal tool for transferring of GOLOS tokens to other people outside the blockchain. There are two ways to redeem the code: transfer its balance to your account or register a new account using it.", @@ -847,64 +630,10 @@ "APP_NAME_password_backup_required": "%(APP_NAME)s Password Backup (required)", "after_printing_write_down_your_user_name": "After printing, write down your user name" }, - "convert_assets_jsx": { - "description": [ - "Here you can sell or buy tokens.", - "Instantly and just in two clicks." - ], - "sell": "sell", - "buy": "buy", - "sell_amount": "Sell amount:", - "buy_amount": "Buy amount:", - "amount_with_fee": "Amount with fee:", - "price": "Price per ", - "no_tradables": "Cannot exchange this asset", - "please_authorize": "Please authorize.", - "too_low_amount": "Amount is too low, so order will be added into market queue.", - "too_much_amount1": "Instantly we can exchange ", - "too_much_amount2": " only (you will receive ", - "too_much_amount2a": " only (you will pay ", - "too_much_amount3": "). The rest of the funds ", - "too_much_amount4": " will be added into market queue.", - "no_orders_DIRECTION": "No tokens in %(DIRECTION)s at current moment. Order will be placed into market queue.", - "too_big_price": "No orders for that price, so exchange can take up to few days.", - "order_can_be_canceled": "If exchange will not occur even in the future, you can cancel the order, and funds will be refunded instantly.", - "coinmarketcap_value": "Value: ", - "finished": "Exchange is successful!", - "finished_desc": "Funds are on ", - "finished_balance": "your balance", - "partly": "Exchange proceed partly...", - "partly_desc": "There is remain to receive ", - "partly_desc2": ". Maybe older will proceed in few days. You can ", - "partly_link": "cancel the order.", - "partly_desc3": "Received amount is on ", - "not": "Exchange not yet proceed...", - "not_desc": "Exchange takes more time than usually. Maybe older will proceed in few days. If no, or if you cannot wait, you can ", - "price_warning": "This price is above than recommended one. Continue?" - }, "time_versions_jsx": { "rev": "rev.", "version_NUM": "(rev. %(NUM)s)" }, - "tips_js": { - "tip_balance_hint": "Balance that you use to reward users and that you use to earn rewards for yourself. Tokens from it can also be sent to increase the Golos Power.", - "tradeable_tokens_that_may_be_transferred_anywhere_at_anytime": "Tradable digital tokens that can be transferred anywhere at any time.", - "LIQUID_TOKEN_can_be_converted_to_VESTING_TOKEN_in_a_process_called_powering_up": "For example, converted to Golos Power or sent to the TIP balance for rewards.", - "tokens_worth_about_AMOUNT_of_LIQUID_TOKEN": "Tokens worth about %(TOKEN_WORTH)s in %(LIQUID_TICKER)s.", - "influence_tokens_which_give_you_more_control_over": "The more Golos Power you have, the higher the percentage goes to the TIP balance and the higher the impact on the project development by selecting delegates and supporting applications for funding from the workers foundation.", - "the_more_you_hold_the_more_you_influence_post_rewards": "The more you hold the more you influence post rewards and earn for accurate voting.", - "vesting_emission_per_day": "~ %(EMISSION_STAKE)s daily", - "vesting_emission_per_day_title": "Amount that, depending on the Golos Power, is transferred daily to the TIP balance\n(your share of the blockchain token issue per day)", - "the_estimated_value_is_based_on_an_average_value_of_steem_in_US_dollars": "The estimated cost is calculated on an 7-day average cost of %(LIQUID_TOKEN)s.", - "VESTING_TOKEN_is_non_transferrable_and_requires_convert_back_to_LIQUID_TOKEN": "%(VESTING_TOKEN2)s is non-transferrable and requires 1 month (4 payments) to convert back to %(LIQUID_TOKEN)s.", - "converted_VESTING_TOKEN_can_be_sent_to_yourself_but_can_not_transfer_again": "Converted %(VESTING_TOKEN)s can be sent to yourself or someone else but can not transfer again without converting back to %(LIQUID_TOKEN)s.", - "profile": "Profile", - "send_to_account": "Send to account", - "confirm_email": "Confirm Email", - "confirm_phone": "Please verify your phone number", - "authenticate_for_this_transaction": "Authenticate for this transaction", - "login_to_your_APP_NAME_account": "Login to your %(APP_NAME)s Account" - }, "promote_post_jsx": { "promote_post": "Promote Post", "buy_gbg": "you can buy GBG on the", @@ -1067,18 +796,6 @@ "add_account": "Add Account", "logout_all": "Log out all accounts" }, - "mobilevalidation_js": { - "not_be_empty": "not be empty", - "be_longer": "be longer", - "have_only_digits": "have only digits", - "you_can_change_your_number": "You can select another one phone number.", - "select_another_number": "Select another phone", - "waiting_from_you": "Waiting SMS message with confirmation code %(code)s from your number to our verification number %(phone)s.", - "waiting_from_you_line_1": "Please, send SMS with text %(code)s on %(phone)s", - "waiting_from_you_line_2": "Golos.id interested in registration real people, not robots. We ask you to send SMS.", - "attempts_10": "Confirmation was attempted a moment ago. You can try again only in 10 seconds", - "attempts_300": "Confirmation was attempted a moment ago. You can try again only in 5 minutes" - }, "chainvalidation_js": { "account_name_should": "Account name should ", "not_be_empty": "not be empty.", @@ -1172,34 +889,6 @@ "verified_exchange_no_memo": "You must include a memo for your exchange transfer, according to exchanger's rules.", "verified_exchange_liquid_only": "You may exchange tokens from GOLOS balance only." }, - "userwallet_jsx": { - "conversion_complete_tip": "Will complete on", - "in_conversion": "%(amount)s in conversion", - "transfer_to_savings": "Transfer to Savings", - "power_up": "Power Up", - "transfer_to_tip": "Transfer to TIP balance", - "donate": "Donate", - "power_down": "Power Down", - "market": "Market", - "convert_to_DEBT_TOKEN": "Convert to %(DEBT_TOKEN)s", - "convert_to_LIQUID_TOKEN": "Convert to %(LIQUID_TOKEN)s", - "withdraw_LIQUID_TOKEN": "Withdraw GOLOS", - "withdraw_DEBT_TOKENS": "Withdraw GBG", - "tokens_worth_about_1_of_LIQUID_TICKER": "Tradable digital tokens that, the price of which tends to the value of ~1 mg of gold in GOLOS tokens.", - "balances": "BALANCES", - "buy_LIQUID_TOKEN_or_VESTING_TOKEN": "Buy %(LIQUID_TOKEN)s or %(VESTING_TOKEN)s", - "savings": "SAVINGS", - "estimated_account_value": "Estimated Account Value", - "next_power_down_to_happen": "Power down in the size of", - "next_power_down_is_scheduled": "scheduled", - "transfers_are_temporary_disabled": "Transfers are temporary disabled.", - "cancel_power_down": "Cancel Power Down", - "top_dpos": "User rating on", - "account_idleness": "Your account has not had any actions using the active key for a long time", - "worker_foundation": "Workers foundation", - "history_viewing": "Operation history", - "history": "HISTORY" - }, "user_saga_js": { "image_upload": { "uploading": "Uploading", @@ -1227,22 +916,6 @@ "missing_active_authority": "Missing Active Authority", "voting_weight_is_too_small": "Voting weight is too small, please accumulate more voting power or steem power" }, - "delegatevestingshares_jsx":{ - "form_title": "Delegate %(VESTING_TOKEN2)s", - "delegate": "Delegate", - "edit": "Edit", - "emission_interest": "With right to receive %% emission for these tokens", - "revoke": "Revoke", - "interest": "%% Of Curate Rewards", - "interest_hint": "To account will be able to receive more curation rewards. This is a %% of your interest.", - "interest_short": "%% Of Interest", - "interest_short_hint_delegator": "That part of delegatee's curation rewards (on delegated GP) will be returned to you", - "interest_short_hint_delegatee": "That part of yours curation rewards (on delegated GP) will be returned to delegator", - "interest_short_hint_neutral": "That part of delegatee's curation rewards (on delegated GP) will be returned to delegator", - "too_large_percent": "Too large percent", - "vdo_exists": "You already have delegated some GP to this account. You could edit its amount, or revoke it at all.", - "no_interest_emission": "none (emission)" - }, "post_editor": { "new_editor": "Markdown", "html_editor": "HTML editor", @@ -1307,21 +980,6 @@ "confirm": "Confirmation", "prompt": "Enter the data" }, - "powerdown_jsx": { - "power_down": "Power Down", - "amount": "Amount", - "already_power_down": "You are already powering down %(AMOUNT)s %(LIQUID_TICKER)s (%(WITHDRAWN)s %(LIQUID_TICKER)s paid out so far). Note that if you change the power down amount the payout schedule will reset.", - "delegating": "You are delegating %(AMOUNT)s %(LIQUID_TICKER)s. That amount is locked up and not available to power down until the delegation is removed and a full reward period has passed.", - "per_week": "That's ~%(AMOUNT)s %(LIQUID_TICKER)s per week.", - "warning": "Leaving less than %(AMOUNT)s %(VESTING_TOKEN)s in your account is not recommended and can leave your account in a unusable state.", - "error": "Unable to power down (ERROR: %(MESSAGE)s)" - }, - "delegate_vesting_shares_info_jsx": { - "confirm_title": "Cancel %(VESTING_TOKENS)s delegation", - "confirm_cancel_delegation": "Are you sure you want to cancel %(VESTING_TOKEN2)s delegation (you will receive tokens back during 7 days)?", - "from": "From", - "to": "To" - }, "faq_jsq": { "page_title": "Frequently Asked Questions", "page_description": "This page contains popular user questions and official Golos Blockchain communication channels.", diff --git a/app/locales/ru-RU.json b/app/locales/ru-RU.json index e0e67f904..a0b754640 100644 --- a/app/locales/ru-RU.json +++ b/app/locales/ru-RU.json @@ -11,10 +11,6 @@ "TOKEN_WORTH": "~1 мг золота", "TIP_TOKEN": "TIP-баланс" }, - "authorrewards_jsx": { - "estimated_author_rewards_last_week": "Оценочные авторские вознаграждения за неделю", - "author_rewards_history": "История авторских наград" - }, "category_selector_jsx": { "tag_your_story": "Добавьте тэги, первый станет категорией.", "select_a_tag": "Выбрать тэг", @@ -35,18 +31,6 @@ "add_account": "Добавить аккаунт", "logout_all": "Выйти из всех аккаунтов" }, - "mobilevalidation_js": { - "not_be_empty": "не может быть пустым", - "be_longer": "должен быть длиннее", - "have_only_digits": "должен состоять только из цифр", - "you_can_change_your_number": "Если вы ошиблись во время ввода адреса почты, вы можете ввести его заново.", - "select_another_number": "Указать другую почту", - "waiting_from_you": "Отправьте код %(code)s на номер %(phone)s для подтверждения вашего номера телефона.", - "waiting_from_you_line_1": "отправьте SMS с кодом %(code)s на номер %(phone)s", - "waiting_from_you_line_2": "Мы заинтересованы в регистрации реальных людей, а не ботов. Именно поэтому мы просим подтвердить вашу почту.", - "attempts_10": "На указанный ранее номер был выслан код. Изменить номер можно через 10 секунд", - "attempts_300": "На один номер мы можем зарегистрировать только одну учетную запись. Ввести новый номер можно через пять минут." - }, "chainvalidation_js": { "account_name_should": "Имя аккаунта должно ", "not_be_empty": "не может быть пустым.", @@ -78,24 +62,6 @@ "window_confirm": "На сегодня вы достигли лимита данных операций. Хотите отправить операцию за %(AMOUNT)s с TIP-баланса?", "window_error": "На сегодня вы достигли лимита данных операций. Отправка операции возможна за %(AMOUNT)s на TIP-балансе. Недостаточно средств!" }, - "key_file": { - "file_title": "Keys Of @", - "file_desc": "There are private keys, which are authorizing your account. Store this file in secure place.", - "password_desc": "Password (use it to log in on Golos): ", - "posting_desc": "Posting Key (also can be used as Golos password): ", - "active_desc": "Active Key: ", - "owner_desc": "Owner Key: ", - "memo_desc": "Memo Key: " - }, - "curationrewards_jsx": { - "curation_rewards_last_24_hours": "Кураторские награды за последние 24 часа", - "daily_average_curation_rewards": "Среднесуточные кураторские награды", - "estimated_curation_rewards_last_week": "Оценочные кураторские награды за неделю", - "curation_rewards_last_week": "Кураторские награды за неделю", - "curation_rewards_history": "История кураторских наград", - "replenish_golos_power": "Пополните Силу Голоса", - "replenish_golos_power_AMOUNT": " на %(AMOUNT)s, чтобы начать получать свой процент за кураторство." - }, "explorepost_jsx": { "copied": "Скопировано!", "copy": "Скопировать", @@ -147,15 +113,6 @@ "mention_comment": "комментарии ", "you": "вас" }, - "filled_orders_jsx": { - "title": "История сделок на бирже", - "orders_info": "На блокчейне Голос есть свой DEX (decentralized exchange - децентрализованная биржа), а значит обменивать токены GOLOS и UIA (эмитированные пользователями активы к другим криптовалютам) возможно прямо здесь.", - "empty_NAME": "У %(NAME)s еще не было ни одной сделки. Купить и продать токены можно на ", - "empty2": "бирже", - "exchanged": "Обмен %(AMOUNT)s %(OWNER)s на %(AMOUNT2)s %(OWNER2)s", - "quick_convert": "Быстрый обмен", - "open_market": "Перейти к бирже" - }, "g": { "golos_fest": "Новости", "APP_NAME": "Голос", @@ -356,15 +313,6 @@ "passwords_do_not_match": "Пароли не совпадают", "no_topics_by_order_found": "В категории %(order)s за последние 7 дней постов нет...", "you_need_private_password_or_key_not_a_public_key": "Вам нужен приватный (не публичный) ключ ...", - "the_rules_of_APP_NAME": { - "one": "1. Не теряйте свой пароль.", - "second": "2. Это серьёзно, запишите сгенерированный пароль.", - "third": "3. Мы не сможем восстановить его.", - "fourth": "4. Если вы легко можете запомнить пароль, значит не так он и безопасен.", - "fifth": "5. Лучше используйте сгенерированные случайным образом пароли.", - "sixth": "6. Никому не говорите свой пароль.", - "seventh": "7. Всегда надёжно храните свой пароль." - }, "current_password": "Текущий пароль", "generated_password": "Сгенерированный пароль", "recover_password": "Восстановить доступ", @@ -525,17 +473,6 @@ "replies_to": "Ответы на", "comments_by": "Комментарии" }, - "exchanges_jsx": { - "title": "Покупка и продажа токенов", - "temporarily_disabled": "Ввод/вывод токенов временно отключён", - "guide_user": "Инструкция по интерфейсу биржи", - "pause": "Ввод/вывод токенов временно приостановлен...", - "total_supply": "Информация об общем количестве токенов", - "other_options": "за RUB / UAH / USD, банковские карты и многие другие варианты", - "questions": "Есть вопросы", - "community_chat": "Получить ответы можно как в чате сообщества", - "delegate_chat": "так и в чате делегатов" - }, "loginform_jsx": { "you_need_a_private_password_or_key": "Вам нужен приватный (не публичный) ключ ...", "cryptography_test_failed": "Криптографический тест провален", @@ -596,42 +533,6 @@ "N_comments": "%(N)s комментариев", "N_comments_2": "%(N)s комментария" }, - "market_jsx": { - "action": "Действие", - "date_created": "Дата создания", - "date_trade": "Дата сделки", - "last_price": "Цена", - "24h_volume": "Объём за 24 часа", - "spread": "Разница (спред)", - "total": "Итого", - "available": "Доступно", - "lowest_ask": "Лучшая цена", - "highest_bid": "Лучшая цена", - "buy_orders": "Заказы на покупку", - "sell_orders": "Заказы на продажу", - "trade_history": "История сделок", - "open_orders": "Открытые сделки", - "sell_amount_for_atleast": "Продать %(amount_to_sell)s за %(min_to_receive)s по цене (%(effectivePrice)s)", - "buy_atleast_amount_for": "Купить по крайней мере %(min_to_receive)s за %(amount_to_sell)s (%(effectivePrice)s)", - "price_warning_above": "Эта цена выше текущей по рынку %(marketPrice)s, вы уверены?", - "price_warning_below": "Эта цена ниже текущей по рынку %(marketPrice)s, вы уверены?", - "order_cancel_confirm": "Отменить заказ %(order_id)s от %(user)s?", - "order_cancel_confirm_few": "Отменить %(order_cnt)s заказ(-а) от %(user)s?", - "order_cancel_all_confirm": "Отменить все заказы между %(symbol1)s и %(symbol2)s от %(user)s?", - "order_cancelled": "Заказ %(order_id)s отменен.", - "higher": "Дороже", - "lower": "Дешевле", - "total_DEBT_TOKEN_SHORT_CURRENCY_SIGN": "Сумма %(DEBT_TOKEN_SHORT)s", - "not_exists": " не существует", - "forbids": " запрещает торговлю с ", - "asset_problem_go_home": "Выбрать другую пару", - "asset_is_overridable": " может отзывать токены с баланса!", - "asset_": "Эмитент актива ", - "market_fee_percent_": "Комиссия ", - "market_depth_": "глубина рынка", - "market_pair": "Торговая пара", - "orders_canceled": "Ордеры успешно отменены." - }, "navigation": { "about": "О проекте", "explore": "Исследовать", @@ -855,41 +756,6 @@ "APP_NAME_password_backup_required": "%(APP_NAME)s резервное копирование пароля (обязательно!)", "after_printing_write_down_your_user_name": "После печати запишите ваше имя пользователя" }, - "convert_assets_jsx": { - "description": [ - "Здесь можно купить или продать токены.", - "В пару кликов и мгновенно." - ], - "sell": "продать", - "buy": "купить", - "sell_amount": "Сумма продажи:", - "buy_amount": "Сумма покупки:", - "amount_with_fee": "Сумма с комиссией:", - "price": "Цена за ", - "no_tradables": "Этот актив не меняется", - "please_authorize": "Пожалуйста, авторизуйтесь.", - "too_low_amount": "Сумма слишком маленькая, сделка будет поставлена в очередь на бирже.", - "too_much_amount1": "Обмен ", - "too_much_amount2": " (вы получите ", - "too_much_amount2a": " (вы заплатите ", - "too_much_amount3": "). Остальные ", - "too_much_amount4": " будут поставлены в очередь на бирже.", - "no_orders_DIRECTION": "Сейчас нет токенов по направлению %(DIRECTION)s. Сделка будет поставлена в очередь на бирже.", - "too_big_price": "Пока что нет ордеров по такой цене, так что обмен займет до нескольких дней.", - "order_can_be_canceled": "Если обмена не произойдет и в дальнейшем, вы сможете отменить сделку в кошельке и средства будут мгновенно возвращены.", - "coinmarketcap_value": "Ценность: ", - "finished": "Обмен прошел успешно!", - "finished_desc": "Токены уже лежат на ", - "finished_balance": "вашем балансе", - "partly": "Обмен прошел частично...", - "partly_desc": "Осталось получить ", - "partly_desc2": ". Возможно, сделка пройдет в течение нескольких дней. Вы можете ", - "partly_link": "отменить сделку", - "partly_desc3": "Полученная часть суммы лежит на ", - "not": "Обмен пока не прошел...", - "not_desc": "Обмен идет дольше обычного. Возможно, он пройдет за несколько дней. Если этого не произойдет или если вы не можете ждать, то можно ", - "price_warning": "Эта цена выше текущей (менее выгодна для вас), чем рыночная. Продолжить обмен?" - }, "time_versions_jsx": { "rev": "ред.", "version_NUM": "(версия %(NUM)s)" @@ -898,25 +764,6 @@ "rev": "ред.", "version_NUM": "(версия %(NUM)s)" }, - "tips_js": { - "tip_balance_hint": "Для вознаграждений пользователей, и на который получаете вознаграждения сами. Токены с него можно также отправить на увеличение Силы Голоса.", - "tradeable_tokens_that_may_be_transferred_anywhere_at_anytime": "Торгуемые цифровые токены, которые могут переданы куда угодно в любой момент.", - "LIQUID_TOKEN_can_be_converted_to_VESTING_TOKEN_in_a_process_called_powering_up": "Например, сконвертированы в %(VESTING_TOKEN2)s, отправлены на TIP-баланс для вознаграждений или биржу.", - "tokens_worth_about_AMOUNT_of_LIQUID_TOKEN": "Перемещаемые цифровые токены, цена которых всегда равна %(amount)s в %(LIQUID_TOKEN)s.", - "influence_tokens_which_give_you_more_control_over": "Чем больше СГ, тем значимее доля от эмиссии на TIP-баланс и выше влияние на развитие проекта путём выбора делегатов, поддержки заявок воркеров на финансирование из фонда.", - "the_more_you_hold_the_more_you_influence_post_rewards": "Чем больше у вас %(VESTING_TOKENS)s, тем сильней вы влияете на вознаграждения, выбор делегатов и поддержку заявок воркеров.'", - "vesting_emission_per_day": "~ %(EMISSION_STAKE)s ежедневно", - "vesting_emission_per_day_title": "Сумма, которая в зависимости от Силы Голоса ежедневно поступает на TIP-баланс\n(ваша доля от эмиссии токенов блокчейна в сутки)", - "the_estimated_value_is_based_on_an_average_value_of_steem_in_US_dollars": "Рассчитывается из 7-ми дневной средней стоимости токенов Голос.", - "VESTING_TOKEN_is_non_transferrable_and_requires_convert_back_to_LIQUID_TOKEN": "Вам потребуются 4 недели (вывод раз в неделю равными долями), чтобы перевести её обратно в ликвидные токены Голос.", - "converted_VESTING_TOKEN_can_be_sent_to_yourself_but_can_not_transfer_again": "Конвертированная %(VESTING_TOKEN)s может быть отправлена себе или кому-то еще, но не может быть передана вновь без конвертирования назад в %(LIQUID_TOKEN)s.", - "profile": "Профиль", - "send_to_account": "Отправить аккаунту", - "confirm_email": "Подтвердить email", - "confirm_phone": "Пожалуйста подтвердите ваш телефон", - "authenticate_for_this_transaction": "Авторизуйтесь для этой транзакции", - "login_to_your_APP_NAME_account": "Зайдите в ваш %(APP_NAME)s аккаунт" - }, "transfer_jsx": { "amount_is_in_form": "Сумма должна быть в формате 99999.999", "insufficient_funds": "Недостаточно средств", @@ -1027,99 +874,6 @@ }, "account_frozen": "Аккаунт временно деактивирован." }, - "assets_jsx": { - "assets_info": "User Issued Assets - эмитированные пользователями активы (токены), которыми можно вознаграждать других, торговать на внутренней бирже, обмениваться, использовать в различных сервисах на блокчейне.", - "create_asset": "Создание актива", - "symbol": "Тикер", - "symbol_too_short": "Не менее 3 символов", - "subsymbol_too_short": "Слишком короткий тикер второго уровня", - "symbol_exists": "Такой тикер уже существует", - "top_symbol_not_your": "Тикер верхнего уровня не принадлежит вам", - "top_symbol_not_exists": "Тикер верхнего уровня не существует", - "max_supply": "Максимальное количество", - "precision": "Кол-во знаков после запятой", - "allow_fee": "Разрешить устанавливать торговую комиссию", - "allow_override_transfer": "Разрешить отзывать токены эмитентом актива (TIP-баланс будет недоступен)", - "allow_danger_note": "Вы не сможете изменить разрешения после создания актива!", - "create_btn": "Создать актив", - "description": "Ссылка на описание токена", - "image_with_text": "Ссылка на изображение тикера (48x48)", - "my_assets": "Мои активы", - "all_assets": "Все активы", - "load_more": "Показать все", - "anti_load_more": "Свернуть", - "creator": "Эмитент", - "supply_count": "Выпущено токенов", - "issue_btn": "Выпустить", - "update_btn": "Изменить", - "balance": "основной баланс", - "tip_balance": "TIP-баланс", - "overridable_no_tip": "Отзывные токены не поддерживают TIP-баланс", - "update_asset": "Изменение актива", - "fee_percent": "Торговая комиссия (%%)", - "fee_not_allowed": "У этого актива торговая комиссия запрещена при создании", - "symbols_whitelist": "Активы, к которым разрешено торговать (каждый в новой строке, если пусто - ко всем)", - "transfer_asset_btn": "Передать права на актив", - "transfer_asset_btn2": "Передать", - "transfer_asset": "Передача актива", - "transfer_new_owner": "Кому", - "cannot_transfer_to_oneself": "Нельзя передать актив самому себе.", - "trade_asset": "Перейти к торгам на внутренней бирже", - "mute_asset": "Не показывать этот актив", - "mute_asset_confirm_TICKER": "Подтвердить скрытие %(TICKER)s? Вернуть актив можно будет в разделе Настройки вашего профиля.", - "deposit": "Депозит", - "withdrawal": "Вывод" - }, - "asset_edit_deposit_jsx": { - "title": "Правила депозита", - "to_type_fixed": "Фиксированный адрес", - "to_type_transfer": "Запрос адреса шлюза", - "to_type_api": "Запрос адреса с API", - "to_fixed": "Куда завести средства (адрес/аккаунт):", - "to_transfer": "Куда отправить 0.001 GOLOS, чтобы получить адрес:", - "to_api": "Шаблон URL для запроса, в шаблон будет вставляться имя текущего пользователя:", - "to_api_example": "Пример: https://site.ru/get_address/", - "to_api_confirm": "Ваше API же сможет обработать, например, такой запрос: ", - "to_api_description": "URL должен возвращать ответ в формате JSON-объекта.\nПример ответа:", - "to_api_error_url": "URL должен начинаться с http:// или https://", - "to_api_error": "Шаблон должен содержать - место для имени.", - "memo_fixed": "Заметка/memo:", - "memo_transfer": "Заметка (к переводу 0.001 GOLOS):", - "transfer_title_SYM": "Депозит %(SYM)s", - "transfer_desc": "Для получения адреса пополнения отправьте 0.001 GOLOS на аккаунт ", - "transfer_desc_2": " с заметкой ", - "waiting": "Перевод 0.001 GOLOS отправлен. Ответный трансфер с адресом поступит в течение ~5-10 минут...", - "timeouted": "Трансфер с адресом не получен. Проверьте кошелек позднее или обратитесь к эмитенту ", - "api_error": "Не удается получить адрес. Попробуйте позднее. Если проблема сохраняется, свяжитесь с эмитентом ", - "api_error_details": "и сообщите подробности ошибки:" - }, - "asset_edit_withdrawal_jsx": { - "title": "Правила вывода", - "to": "Отправить токены на адрес/аккаунт: ", - "min_amount": "Минимальная сумма: ", - "details": "Дополнительно: ", - "unavailable": "Шлюз на техобслуживании", - "way_name": "Название сети: ", - "way_name_placeholder": "USDT (TRC-20)", - "way_memo": "Заметка к переводу: ", - "way_memo_placeholder": "адрес-USDT-кошелька", - "way_prefix": "Префикс заметки (если нужен): ", - "way_prefix_placeholder": "tron:", - "way_add": "Добавить способ вывода", - "way_name_error": "Укажите название сети/системы, например: USDT (TRC-20), AdvCash, QIWI (на телефон).", - "way_prefix_error": "Префикс не нужно указывать в самой заметке.", - "wrong_prefix_end": "Префикс должен заканчиваться на один из символов: : - _", - "wrong_prefix_start": "Префикс не должен начинаться с пробела.", - "wrong_memo_start": "Заметка не должна начинаться с пробела.", - "wrong_memo_end": "Заметка не должна заканчиваться пробелом.", - "transfer_title_SYM": "Вывод %(SYM)s", - "transfer_desc": "Вывод средств с баланса.", - "transfer_by": "через", - "transfer_way": "куда", - "transfer_no_memo": "Для вывода средств укажите заметку", - "no_way_error": "Если вы заполнили одно из полей, то вы должны указать хотя бы один способ вывода или заполнить Дополнительно.", - "no_to_error": "Если вы указали вывод через перевод с заметкой, то укажите, куда переводить." - }, "invites_jsx": { "create_invite": "Создание чека", "create_invite_info": "Чеки (инвайт-коды) — инструмент для передачи токенов другим людям вне блокчейна. Использовать чек можно двумя способами: перевести его баланс на аккаунт (форма для этого ниже) или зарегистрировать с его помощью новый аккаунт.", @@ -1155,35 +909,6 @@ "the_private_key_or_password_should_be_kept_offline": "Приватный ключ владельца (если вы его получали) и главный пароль лучше хранить в оффлайне.", "the_memo_key_is_used_to_create_and_read_memos": "Memo-ключ для зашифрованных заметок и обмена сообщениями через мессенджер." }, - "userwallet_jsx": { - "conversion_complete_tip": "Завершается", - "in_conversion": "%(amount)s на конвертации", - "transfer_to_savings": "Перевести в сейф", - "power_up": "Увеличить Силу Голоса", - "transfer_to_tip": "Пополнить TIP-баланс", - "donate": "Отблагодарить", - "transfer_to_liquid": "Пополнить основной баланс", - "power_down": "Уменьшить Силу Голоса", - "market": "Внутренняя биржа", - "convert_to_DEBT_TOKEN": "Конвертация в GBG", - "convert_to_LIQUID_TOKEN": "Конвертация в GOLOS", - "withdraw_LIQUID_TOKEN": "Вывести GOLOS на основной баланс", - "withdraw_DEBT_TOKENS": "Вывести GBG на основной баланс", - "tokens_worth_about_1_of_LIQUID_TICKER": "Торгуемые на внутренней бирже токены, цена которых равна стоимости %(TOKEN_WORTH)s в токенах ГОЛОС.", - "balances": "БАЛАНСЫ", - "buy_LIQUID_TOKEN_or_VESTING_TOKEN": "Купить %(LIQUID_TOKEN)s или %(VESTING_TOKEN2)s", - "savings": "СЕЙФ", - "estimated_account_value": "Оценочная стоимость аккаунта", - "next_power_down_to_happen": "Понижение Силы Голоса в размере", - "next_power_down_is_scheduled": "запланировано", - "transfers_are_temporary_disabled": "Переводы временно отключены.", - "cancel_power_down": "Отменить понижение Силы Голоса", - "top_dpos": "Рейтинг пользователей на", - "account_idleness": "На аккаунте давно не было действий с использованием активного ключа", - "worker_foundation": "Фонд сообщества", - "history_viewing": "История операций", - "history": "ИСТОРИЯ" - }, "votesandcomments_jsx": { "no_responses_yet_click_to_respond": "Комментариев пока нет. Нажмите, чтобы перейти к обсуждению.", "response_count": { @@ -1233,43 +958,6 @@ "please_remove_following_html_elements": "Пожалуйста, удалите эти HTML элементы из вашего поста: ", "payouts_declined": "Выплаты из пула отключены" }, - "witnesses_jsx": { - "witness_info": "Делегаты - это избираемые голосованием сообщества пользователи, которые поддерживают работу своих нод блокчейна и принимают решения об обновлении кода, также занимаются и другими вопросами, связанными с развитием проекта.", - "witness_thread": "пост делегата", - "top_witnesses": "Топ делегатов", - "what_is_api": "API-ноды имеют повышенные требования к ресурсам сервера, именно через них происходят запросы к блокчейну на получение/отправку информации, они важны для развития различных клиентов и сервисов...", - "what_is_seed": "SEED-ноды являются основой обмена данными для функционирования блокчейна, принимая и раздавая блоки они разгружают пропускную способность сети, снижая отклик для территориально близких подключений...", - "approval": "СГ в поддержку", - "missed_1": "Пропущено", - "missed_2": "блоков", - "last_block": "Последний блок", - "price_feed": "Прайс фид", - "props": "Параметры", - "reg_fee": "Комиссия за регистрацию", - "apr": "APR по GBG", - "block_size": "Размер блока", - "witness_deactive": "Делегат приостановил подпись блоков", - "no_price_feed": "Делегат не опубликовал прайс фид", - "version": "Версия", - "you_can_vote_for_maximum_of_witnesses": "Вы можете голосовать максимум за 30 делегатов", - "chat_delegates": "Чат делегатов", - "chain_properties": "Параметры сети", - "witness": "Делегаты", - "witness_0": "Вы пока не поддержали ни одного делегата", - "witness_1": "делегата с силой", - "witness_2": "делегатов с силой", - "witness_supported": "Вы поддержали", - "witness_addon": "за каждого из них", - "information": "Информация", - "load_more": "Показать еще", - "if_you_want_to_vote_outside_of_top_enter_account_name": "Если вы хотите проголосовать за делегата вне TOP 100, введите имя аккаунта ниже", - "set_witness_proxy": "Вы также можете выбрать прокси, который будет вместо вас голосовать за делегатов. Это сбросит ваш текущий выбор делегатов.", - "witness_set": "Вы установили прокси для голосования за делегатов. Хотите голосовать сами - отмените его.", - "witness_proxy_current": "Ваш текущий прокси", - "witness_proxy_set": "Установить прокси", - "witness_proxy_clear": "Отменить прокси", - "proxy_update_error": "Ваш прокси не был обновлен" - }, "user_saga_js": { "image_upload": { "uploading": "Загрузка", @@ -1299,22 +987,6 @@ "user_blocked_user": "Пользователь заблокировал вас. Теперь это действие только за плату - с TIP-баланса.", "user_blocked_user_no_tip": "Пользователь заблокировал вас." }, - "delegatevestingshares_jsx":{ - "form_title": "Делегировать %(VESTING_TOKEN2)s", - "delegate": "Делегировать", - "edit": "Изменить", - "emission_interest": "С правом получения %% эмиссии на эти токены", - "revoke": "Отозвать", - "interest": "%% возврата с кураторских выплат", - "interest_hint": "Пользователь сможет получать больше кураторских выплат. Выберите %%, сколько от этой надбавки возвращать вам.", - "interest_short": "%% возврата", - "interest_short_hint_delegator": "Вы будете получать часть кураторских выплат получателя, начисляемых на делегированную СГ", - "interest_short_hint_delegatee": "Делегатору будет возвращаться часть кураторских выплат, начисляемых вам на делегированную СГ", - "interest_short_hint_neutral": "Делегатору будет возвращаться часть кураторских выплат получателя, начисляемых ему на делегированную СГ", - "too_large_percent": "Слишком большой процент", - "vdo_exists": "Вы уже делегировали этому аккаунту. Вы можете изменить количество СГ или отозвать делегирование.", - "no_interest_emission": "с правом на эмиссию" - }, "post_editor": { "new_editor": "Markdown", "html_editor": "HTML редактор", @@ -1380,21 +1052,6 @@ "confirm": "Подтверждение", "prompt": "Введите данные" }, - "powerdown_jsx": { - "power_down": "Уменьшить Силу Голоса", - "amount": "Количество", - "already_power_down": "Вы уже запустили понижение СГ на %(AMOUNT)s %(LIQUID_TICKER)s (из них %(WITHDRAWN)s выплачено). Если вы измените количество, отсчёт недель понижения начнётся заново.", - "delegating": "Вы делегировали %(AMOUNT)s %(LIQUID_TICKER)s. Эти токены заблокированы, в случае отмены делегирования они вернутся на баланс через 7 дней.", - "per_week": "Это ~%(AMOUNT)s %(LIQUID_TICKER)s в неделю (в течение 4 недель).", - "warning": "Оставлять меньше чем %(AMOUNT)s %(LIQUID_TICKER)s не рекомендуется, т.к. это может остановить все транзакции с использованием это аккаунта.", - "error": "Не удалось уменьшить Силу Голоса (ERROR: %(MESSAGE)s)" - }, - "delegate_vesting_shares_info_jsx": { - "confirm_title": "Отмена делегирования %(VESTING_TOKENS)s", - "confirm_cancel_delegation": "Вы действительно хотите отозвать делегированную %(VESTING_TOKEN2)s у пользователя %(delegatee)s (токены вернутся на ваш баланс через 7 дней)?", - "from": "От кого", - "to": "Кому" - }, "faq_jsq": { "page_title": "Часто задаваемые вопросы", "page_description": "На этой странице собраны популярные вопросы пользователей и официальные каналы коммуникаций Golos Blockchain", From ed2889ab10bbce63747a8c2e6ca0377291bc33fe Mon Sep 17 00:00:00 2001 From: Lex-Ai <12001684+Lex-Ai@users.noreply.github.com> Date: Thu, 3 Nov 2022 18:55:29 +0300 Subject: [PATCH 10/17] minor fix --- app/components/cards/PostFull.jsx | 7 ++----- app/components/cards/PostFull.scss | 5 +++-- app/components/pages/Post.jsx | 6 +++--- app/components/pages/UserProfile.jsx | 4 ++-- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/components/cards/PostFull.jsx b/app/components/cards/PostFull.jsx index 70dec28b2..7999dfd0e 100644 --- a/app/components/cards/PostFull.jsx +++ b/app/components/cards/PostFull.jsx @@ -48,13 +48,10 @@ function TimeAuthorCategory({ content, authorRepLog10, showTags }) { function TimeAuthorCategoryLarge({ content, authorRepLog10 }) { return ( - - - - - {content.views} / + {content.views} +
.eye { - color: $dark-gray; - margin-right: .4rem; + margin-left: .4rem; + padding-left: .4rem; + border-left: 1px solid $medium-gray; } > .Userpic { margin-top: -4px; diff --git a/app/components/pages/Post.jsx b/app/components/pages/Post.jsx index ed348b015..b27550481 100644 --- a/app/components/pages/Post.jsx +++ b/app/components/pages/Post.jsx @@ -386,11 +386,11 @@ class Post extends React.Component {
{positiveComments} {negativeGroup} - {positiveComments.length ? - (
this.subscribe(e, dis)}> + {positiveComments.length && !subscribed && current_user ? + (
this.subscribe(e, dis)}> - {subscribed ? tt('post_jsx.unsubscribe') : tt('post_jsx.subscribe_comments')} + {subscribed ? tt('post_jsx.unsubscribe_long') : tt('post_jsx.subscribe_comments_long')}
) : null}
diff --git a/app/components/pages/UserProfile.jsx b/app/components/pages/UserProfile.jsx index ba9660f3f..7943a2633 100644 --- a/app/components/pages/UserProfile.jsx +++ b/app/components/pages/UserProfile.jsx @@ -448,8 +448,8 @@ export default class UserProfile extends React.Component { {tt('g.wallet')} {isMyAccount && } - {msgsHost() ? - {tt('g.messages')} {isMyAccount && } + {isMyAccount && msgsHost() ? + {tt('g.messages')} : null} {isMyAccount ? {tt('g.settings')} From cfa02c43695f76db3dbd282fc5a91bae372f1c41 Mon Sep 17 00:00:00 2001 From: Lex-Ai <12001684+Lex-Ai@users.noreply.github.com> Date: Fri, 4 Nov 2022 18:25:31 +0300 Subject: [PATCH 11/17] minor fix --- app/components/modules/Settings.jsx | 30 -- app/components/pages/Post.jsx | 2 +- tests/uiadepo/index.js | 33 -- tests/uiadepo/package.json | 12 - tests/uiadepo/yarn.lock | 666 ---------------------------- 5 files changed, 1 insertion(+), 742 deletions(-) delete mode 100644 tests/uiadepo/index.js delete mode 100644 tests/uiadepo/package.json delete mode 100644 tests/uiadepo/yarn.lock diff --git a/app/components/modules/Settings.jsx b/app/components/modules/Settings.jsx index 951637be3..0c7fd44f3 100644 --- a/app/components/modules/Settings.jsx +++ b/app/components/modules/Settings.jsx @@ -267,17 +267,6 @@ class Settings extends React.Component { }) } - unmuteAsset = (e) => { - const sym = e.currentTarget.dataset.sym; - let mutedUIA = []; - mutedUIA = localStorage.getItem('mutedUIA'); - if (mutedUIA) try { mutedUIA = JSON.parse(mutedUIA) } catch (ex) {} - if (!mutedUIA) mutedUIA = []; - mutedUIA = mutedUIA.filter(o => o !== sym) - localStorage.setItem('mutedUIA', JSON.stringify(mutedUIA)); - window.location.reload() - } - onNotifyPresetChange = e => { let notifyPresets = {...this.state.notifyPresets}; notifyPresets[e.target.dataset.type] = e.target.checked; @@ -308,16 +297,6 @@ class Settings extends React.Component { const following = follow && follow.getIn(['getFollowingAsync', account.name]); const ignores = isOwnAccount && block && block.getIn(['blocking', account.name, 'result']) const mutedInNew = isOwnAccount && props.mutedInNew; - let mutedUIA = []; - if (process.env.BROWSER) { - mutedUIA = localStorage.getItem('mutedUIA'); - if (mutedUIA) try { mutedUIA = JSON.parse(mutedUIA) } catch (ex) {} - if (!mutedUIA) mutedUIA = []; - } - let mutedUIAlist = []; - for (let sym of mutedUIA) { - mutedUIAlist.push(

{sym} X

) - } const {pImageUploading, cImageUploading} = this.state; const languageSelectBox =