mirror of
https://github.com/ecency/ecency-mobile.git
synced 2024-12-23 05:13:04 +03:00
Merge branch 'master' of https://github.com/esteemapp/esteem-mobile
This commit is contained in:
commit
a3739469af
@ -45,30 +45,6 @@ The above copyright notice and this permission notice shall be included in all c
|
|||||||
|
|
||||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
## BugsnagReactNative
|
|
||||||
|
|
||||||
Copyright (c) 2016 Bugsnag, Inc.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
|
|
||||||
## CodePush
|
## CodePush
|
||||||
|
|
||||||
Microsoft CodePush Plugin for React Native
|
Microsoft CodePush Plugin for React Native
|
||||||
@ -321,6 +297,29 @@ SOFTWARE.
|
|||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
|
||||||
|
## JWT
|
||||||
|
|
||||||
|
Copyright (c) 2013 Karma Mobility, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
## QBImagePickerController
|
## QBImagePickerController
|
||||||
|
|
||||||
Copyright (c) 2015 Katsuma Tanaka
|
Copyright (c) 2015 Katsuma Tanaka
|
||||||
@ -384,7 +383,7 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) Facebook, Inc. and its affiliates.
|
Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -429,6 +428,30 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## SSZipArchive
|
||||||
|
|
||||||
|
Copyright (c) 2010-2015, Sam Soffes, http://soff.es
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
## boost-for-react-native
|
## boost-for-react-native
|
||||||
|
|
||||||
Boost Software License - Version 1.0 - August 17th, 2003
|
Boost Software License - Version 1.0 - August 17th, 2003
|
||||||
|
@ -68,36 +68,6 @@ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
|
|||||||
<key>Type</key>
|
<key>Type</key>
|
||||||
<string>PSGroupSpecifier</string>
|
<string>PSGroupSpecifier</string>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
|
||||||
<key>FooterText</key>
|
|
||||||
<string>Copyright (c) 2016 Bugsnag, Inc.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
</string>
|
|
||||||
<key>License</key>
|
|
||||||
<string>MIT</string>
|
|
||||||
<key>Title</key>
|
|
||||||
<string>BugsnagReactNative</string>
|
|
||||||
<key>Type</key>
|
|
||||||
<string>PSGroupSpecifier</string>
|
|
||||||
</dict>
|
|
||||||
<dict>
|
<dict>
|
||||||
<key>FooterText</key>
|
<key>FooterText</key>
|
||||||
<string>Microsoft CodePush Plugin for React Native
|
<string>Microsoft CodePush Plugin for React Native
|
||||||
@ -374,6 +344,35 @@ SOFTWARE.
|
|||||||
<key>Type</key>
|
<key>Type</key>
|
||||||
<string>PSGroupSpecifier</string>
|
<string>PSGroupSpecifier</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>FooterText</key>
|
||||||
|
<string>Copyright (c) 2013 Karma Mobility, Inc.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
</string>
|
||||||
|
<key>License</key>
|
||||||
|
<string>MIT</string>
|
||||||
|
<key>Title</key>
|
||||||
|
<string>JWT</string>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>PSGroupSpecifier</string>
|
||||||
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>FooterText</key>
|
<key>FooterText</key>
|
||||||
<string>Copyright (c) 2015 Katsuma Tanaka
|
<string>Copyright (c) 2015 Katsuma Tanaka
|
||||||
@ -455,7 +454,7 @@ THE SOFTWARE.
|
|||||||
<key>FooterText</key>
|
<key>FooterText</key>
|
||||||
<string>MIT License
|
<string>MIT License
|
||||||
|
|
||||||
Copyright (c) Facebook, Inc. and its affiliates.
|
Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@ -512,6 +511,36 @@ THE SOFTWARE.
|
|||||||
<key>Type</key>
|
<key>Type</key>
|
||||||
<string>PSGroupSpecifier</string>
|
<string>PSGroupSpecifier</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>FooterText</key>
|
||||||
|
<string>Copyright (c) 2010-2015, Sam Soffes, http://soff.es
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
</string>
|
||||||
|
<key>License</key>
|
||||||
|
<string>MIT</string>
|
||||||
|
<key>Title</key>
|
||||||
|
<string>SSZipArchive</string>
|
||||||
|
<key>Type</key>
|
||||||
|
<string>PSGroupSpecifier</string>
|
||||||
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>FooterText</key>
|
<key>FooterText</key>
|
||||||
<string>Boost Software License - Version 1.0 - August 17th, 2003
|
<string>Boost Software License - Version 1.0 - August 17th, 2003
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { PureComponent, Fragment } from 'react';
|
import React, { PureComponent, Fragment } from 'react';
|
||||||
import { View, Text } from 'react-native';
|
import { View, Text, TouchableWithoutFeedback } from 'react-native';
|
||||||
import ActionSheet from 'react-native-actionsheet';
|
import ActionSheet from 'react-native-actionsheet';
|
||||||
import { injectIntl } from 'react-intl';
|
import { injectIntl } from 'react-intl';
|
||||||
|
|
||||||
@ -48,6 +48,7 @@ class CommentView extends PureComponent {
|
|||||||
fetchPost,
|
fetchPost,
|
||||||
handleDeleteComment,
|
handleDeleteComment,
|
||||||
handleOnEditPress,
|
handleOnEditPress,
|
||||||
|
handleOnLongPress,
|
||||||
handleOnReplyPress,
|
handleOnReplyPress,
|
||||||
handleOnUserPress,
|
handleOnUserPress,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
@ -56,12 +57,12 @@ class CommentView extends PureComponent {
|
|||||||
marginLeft,
|
marginLeft,
|
||||||
voteCount,
|
voteCount,
|
||||||
intl,
|
intl,
|
||||||
author,
|
|
||||||
mainAuthor = { mainAuthor },
|
mainAuthor = { mainAuthor },
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { isShowSubComments, isPressedShowButton } = this.state;
|
const { isShowSubComments, isPressedShowButton } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<TouchableWithoutFeedback onLongPress={handleOnLongPress}>
|
||||||
<View>
|
<View>
|
||||||
<PostHeaderDescription
|
<PostHeaderDescription
|
||||||
key={comment.permlink}
|
key={comment.permlink}
|
||||||
@ -73,7 +74,12 @@ class CommentView extends PureComponent {
|
|||||||
isShowOwnerIndicator={mainAuthor === comment.author}
|
isShowOwnerIndicator={mainAuthor === comment.author}
|
||||||
/>
|
/>
|
||||||
<View style={[{ marginLeft: marginLeft || 29 }, styles.bodyWrapper]}>
|
<View style={[{ marginLeft: marginLeft || 29 }, styles.bodyWrapper]}>
|
||||||
<PostBody isComment handleOnUserPress={handleOnUserPress} body={comment.body} />
|
<PostBody
|
||||||
|
isComment
|
||||||
|
handleOnUserPress={handleOnUserPress}
|
||||||
|
body={comment.body}
|
||||||
|
textSelectable={false}
|
||||||
|
/>
|
||||||
<View style={styles.footerWrapper}>
|
<View style={styles.footerWrapper}>
|
||||||
{isLoggedIn && (
|
{isLoggedIn && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@ -165,6 +171,7 @@ class CommentView extends PureComponent {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
</TouchableWithoutFeedback>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,22 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { withNavigation } from 'react-navigation';
|
import { withNavigation } from 'react-navigation';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { injectIntl } from 'react-intl';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
|
||||||
import { getComments, deleteComment } from '../../../providers/steem/dsteem';
|
import { getComments, deleteComment } from '../../../providers/steem/dsteem';
|
||||||
|
|
||||||
// Services and Actions
|
// Services and Actions
|
||||||
|
import { writeToClipboard } from '../../../utils/clipboard';
|
||||||
|
import { toastNotification } from '../../../redux/actions/uiAction';
|
||||||
|
|
||||||
// Middleware
|
// Middleware
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
import { default as ROUTES } from '../../../constants/routeNames';
|
import ROUTES from '../../../constants/routeNames';
|
||||||
|
|
||||||
// Component
|
// Component
|
||||||
import { CommentsView } from '..';
|
import CommentsView from '../view/commentsView';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Props Name Description Value
|
* Props Name Description Value
|
||||||
@ -163,6 +167,27 @@ class CommentsContainer extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_handleCommentCopyAction = (index, selectedComment) => {
|
||||||
|
const { dispatch, intl } = this.props;
|
||||||
|
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
writeToClipboard(`https://steemit.com${get(selectedComment, 'url')}`).then(() => {
|
||||||
|
dispatch(
|
||||||
|
toastNotification(
|
||||||
|
intl.formatMessage({
|
||||||
|
id: 'alert.copied',
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { comments: _comments, selectedPermlink } = this.state;
|
const { comments: _comments, selectedPermlink } = this.state;
|
||||||
const {
|
const {
|
||||||
@ -196,6 +221,7 @@ class CommentsContainer extends Component {
|
|||||||
isLoggedIn={isLoggedIn}
|
isLoggedIn={isLoggedIn}
|
||||||
fetchPost={fetchPost}
|
fetchPost={fetchPost}
|
||||||
handleDeleteComment={this._handleDeleteComment}
|
handleDeleteComment={this._handleDeleteComment}
|
||||||
|
handleCommentCopyAction={this._handleCommentCopyAction}
|
||||||
{...this.props}
|
{...this.props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -208,4 +234,4 @@ const mapStateToProps = state => ({
|
|||||||
pinCode: state.account.pin,
|
pinCode: state.account.pin,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default withNavigation(connect(mapStateToProps)(CommentsContainer));
|
export default withNavigation(connect(mapStateToProps)(injectIntl(CommentsContainer)));
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent, Fragment } from 'react';
|
||||||
import { FlatList } from 'react-native';
|
import { FlatList } from 'react-native';
|
||||||
|
import ActionSheet from 'react-native-actionsheet';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { injectIntl } from 'react-intl';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
import { Comment } from '../../comment';
|
import { Comment } from '../../comment';
|
||||||
|
|
||||||
// Styles
|
// Styles
|
||||||
@ -15,7 +19,10 @@ class CommentsView extends PureComponent {
|
|||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {};
|
this.state = {
|
||||||
|
selectedComment: null,
|
||||||
|
};
|
||||||
|
this.commentMenu = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component Life Cycles
|
// Component Life Cycles
|
||||||
@ -24,6 +31,11 @@ class CommentsView extends PureComponent {
|
|||||||
|
|
||||||
_keyExtractor = item => item.permlink;
|
_keyExtractor = item => item.permlink;
|
||||||
|
|
||||||
|
_openCommentMenu = item => {
|
||||||
|
this.setState({ selectedComment: item });
|
||||||
|
this.commentMenu.current.show();
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
mainAuthor,
|
mainAuthor,
|
||||||
@ -40,9 +52,13 @@ class CommentsView extends PureComponent {
|
|||||||
isShowSubComments,
|
isShowSubComments,
|
||||||
marginLeft,
|
marginLeft,
|
||||||
handleDeleteComment,
|
handleDeleteComment,
|
||||||
|
handleCommentCopyAction,
|
||||||
|
intl,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
const { selectedComment } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<Fragment>
|
||||||
<FlatList
|
<FlatList
|
||||||
data={comments}
|
data={comments}
|
||||||
keyExtractor={this._keyExtractor}
|
keyExtractor={this._keyExtractor}
|
||||||
@ -65,11 +81,27 @@ class CommentsView extends PureComponent {
|
|||||||
isShowSubComments={isShowSubComments}
|
isShowSubComments={isShowSubComments}
|
||||||
key={item.permlink}
|
key={item.permlink}
|
||||||
marginLeft={marginLeft}
|
marginLeft={marginLeft}
|
||||||
|
handleOnLongPress={() => this._openCommentMenu(item)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<ActionSheet
|
||||||
|
ref={this.commentMenu}
|
||||||
|
options={[
|
||||||
|
intl.formatMessage({
|
||||||
|
id: 'post.copy_link',
|
||||||
|
}),
|
||||||
|
intl.formatMessage({
|
||||||
|
id: 'alert.cancel',
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
title={get(selectedComment, 'summary')}
|
||||||
|
cancelButtonIndex={1}
|
||||||
|
onPress={index => handleCommentCopyAction(index, selectedComment)}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default CommentsView;
|
export default injectIntl(CommentsView);
|
||||||
|
@ -1,32 +1,40 @@
|
|||||||
import { replaceBetween } from './utils';
|
import { replaceBetween } from './utils';
|
||||||
|
|
||||||
export default ({ getState, item, setState }) => {
|
export default async ({ getState, item, setState }) => {
|
||||||
let { text } = getState();
|
const states = getState();
|
||||||
const { selection } = getState();
|
let { text } = states;
|
||||||
text = text || '';
|
const { selection } = states;
|
||||||
let newText;
|
let newText;
|
||||||
let newSelection;
|
let newSelection;
|
||||||
if (selection.start !== selection.end) {
|
|
||||||
|
text = text || '';
|
||||||
|
|
||||||
|
const isSelected = selection.start === selection.end;
|
||||||
|
const hasLineBreakOnStart = text.substring(selection.start - 1, selection.start) === '\n';
|
||||||
|
const hasLineBreakOnEnd = text.substring(selection.end - 1, selection.end) === '\n';
|
||||||
|
|
||||||
|
if (!isSelected && hasLineBreakOnStart) {
|
||||||
newText = replaceBetween(
|
newText = replaceBetween(
|
||||||
text,
|
text,
|
||||||
selection,
|
selection,
|
||||||
`${item.prefix} ${text.substring(selection.start, selection.end)}\n`,
|
`${item.prefix} ${text.substring(selection.start, selection.end)}\n`,
|
||||||
);
|
);
|
||||||
newSelection = { start: selection.end + 3, end: selection.end + 3 };
|
newSelection = { start: selection.end + 3, end: selection.end + 3 };
|
||||||
} else if (
|
} else if (!isSelected && !hasLineBreakOnStart) {
|
||||||
selection.start === selection.end &&
|
newText = replaceBetween(
|
||||||
text.substring(selection.end - 1, selection.end) === '\n'
|
text,
|
||||||
) {
|
selection,
|
||||||
|
`\n${item.prefix} ${text.substring(selection.start, selection.end)}\n`,
|
||||||
|
);
|
||||||
|
newSelection = { start: selection.end + 3, end: selection.end + 3 };
|
||||||
|
} else if (isSelected && hasLineBreakOnEnd) {
|
||||||
newText = replaceBetween(text, selection, `${item.prefix} `);
|
newText = replaceBetween(text, selection, `${item.prefix} `);
|
||||||
newSelection = { start: selection.start + 2, end: selection.start + 2 };
|
newSelection = { start: selection.start + 2, end: selection.start + 2 };
|
||||||
} else {
|
} else if (isSelected && !hasLineBreakOnEnd) {
|
||||||
newText = replaceBetween(text, selection, `\n${item.prefix} `);
|
newText = replaceBetween(text, selection, `\n${item.prefix} `);
|
||||||
newSelection = { start: selection.start + 3, end: selection.start + 3 };
|
newSelection = { start: selection.start + 3, end: selection.start + 3 };
|
||||||
}
|
}
|
||||||
|
|
||||||
setState({ text: newText }, () => {
|
await setState({ text: newText, textUpdated: true });
|
||||||
setTimeout(() => {
|
await setState({ newSelection });
|
||||||
setState({ newSelection });
|
|
||||||
}, 300);
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@ -3,15 +3,15 @@ import { isStringWebLink, replaceBetween } from './utils';
|
|||||||
export const writeUrlTextHere = 'https://example.com';
|
export const writeUrlTextHere = 'https://example.com';
|
||||||
export const writeTextHereString = 'Text here';
|
export const writeTextHereString = 'Text here';
|
||||||
|
|
||||||
export default ({ getState, item, setState, isImage = null }) => {
|
export default async ({ getState, item, setState, isImage = null }) => {
|
||||||
const { selection, text } = getState();
|
const { selection, text } = getState();
|
||||||
const imagePrefix = isImage ? '!' : '';
|
const imagePrefix = isImage ? '!' : '';
|
||||||
const itemText = item ? item.text : writeTextHereString;
|
const itemText = item ? item.text : writeTextHereString;
|
||||||
const itemUrl = item ? item.url : writeUrlTextHere;
|
const itemUrl = item ? item.url : writeUrlTextHere;
|
||||||
|
|
||||||
let newText;
|
let newText;
|
||||||
let newSelection;
|
let newSelection;
|
||||||
const selectedText = text.substring(selection.start, selection.end);
|
const selectedText = text.substring(selection.start, selection.end);
|
||||||
|
|
||||||
if (selection.start !== selection.end) {
|
if (selection.start !== selection.end) {
|
||||||
if (isStringWebLink(selectedText)) {
|
if (isStringWebLink(selectedText)) {
|
||||||
newText = replaceBetween(text, selection, `${imagePrefix}[${itemText}](${selectedText})`);
|
newText = replaceBetween(text, selection, `${imagePrefix}[${itemText}](${selectedText})`);
|
||||||
@ -40,7 +40,8 @@ export default ({ getState, item, setState, isImage = null }) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setState({ text: newText }, () => {
|
|
||||||
setState({ newSelection });
|
await setState({ text: newText, textUpdated: true }, async () => {
|
||||||
|
await setState({ newSelection });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -8,19 +8,14 @@ export default ({ getState, item, setState }) => {
|
|||||||
item.wrapper.concat(text.substring(selection.start, selection.end), item.wrapper),
|
item.wrapper.concat(text.substring(selection.start, selection.end), item.wrapper),
|
||||||
);
|
);
|
||||||
let newPosition;
|
let newPosition;
|
||||||
|
|
||||||
if (selection.start === selection.end) {
|
if (selection.start === selection.end) {
|
||||||
newPosition = selection.end + item.wrapper.length;
|
newPosition = selection.end + item.wrapper.length;
|
||||||
} else {
|
} else {
|
||||||
newPosition = selection.end + item.wrapper.length * 2;
|
newPosition = selection.end + item.wrapper.length * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
const extra = {
|
setState({ text: newText, textUpdated: true }, () => {
|
||||||
newSelection: {
|
setState({ newSelection: { start: newPosition, end: newPosition } });
|
||||||
start: newPosition,
|
|
||||||
end: newPosition,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
setState({ text: newText }, () => {
|
|
||||||
setState({ ...extra });
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { replaceBetween } from './utils';
|
import { replaceBetween } from './utils';
|
||||||
|
|
||||||
export default ({ getState, item, setState }) => {
|
export default async ({ getState, item, setState }) => {
|
||||||
const { text, selection } = getState();
|
const { text, selection } = getState();
|
||||||
let newText = replaceBetween(
|
let newText = replaceBetween(
|
||||||
text,
|
text,
|
||||||
@ -41,15 +41,8 @@ export default ({ getState, item, setState }) => {
|
|||||||
)}`,
|
)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const extra = {
|
|
||||||
newSelection: {
|
await setState({ text: newText, textUpdated: true }, async () => {
|
||||||
start: newPosition,
|
await setState({ newSelection: { start: newPosition, end: newPosition } });
|
||||||
end: newPosition,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
setState({ text: newText }, () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
setState({ ...extra });
|
|
||||||
}, 25);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -11,7 +11,6 @@ export default [
|
|||||||
iconType: 'FontAwesome',
|
iconType: 'FontAwesome',
|
||||||
wrapper: '**',
|
wrapper: '**',
|
||||||
onPress: applyWrapFormat,
|
onPress: applyWrapFormat,
|
||||||
// style: { fontWeight: 'bold' },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'I',
|
key: 'I',
|
||||||
|
@ -22,8 +22,13 @@ export default class MarkdownEditorView extends Component {
|
|||||||
this.state = {
|
this.state = {
|
||||||
text: props.draftBody || '',
|
text: props.draftBody || '',
|
||||||
selection: { start: 0, end: 0 },
|
selection: { start: 0, end: 0 },
|
||||||
|
textUpdated: false,
|
||||||
newSelection: null,
|
newSelection: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.inputRef = React.createRef();
|
||||||
|
this.galleryRef = React.createRef();
|
||||||
|
this.clearRef = React.createRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lifecycle functions
|
// Lifecycle functions
|
||||||
@ -47,8 +52,8 @@ export default class MarkdownEditorView extends Component {
|
|||||||
) {
|
) {
|
||||||
applyImageLink({
|
applyImageLink({
|
||||||
getState: this._getState,
|
getState: this._getState,
|
||||||
setState: (state, callback) => {
|
setState: async (state, callback) => {
|
||||||
this.setState(state, callback);
|
await this.setState(state, callback);
|
||||||
},
|
},
|
||||||
item: { url: nextProps.uploadedImage.url, text: nextProps.uploadedImage.hash },
|
item: { url: nextProps.uploadedImage.url, text: nextProps.uploadedImage.hash },
|
||||||
isImage: !!nextProps.uploadedImage,
|
isImage: !!nextProps.uploadedImage,
|
||||||
@ -76,6 +81,14 @@ export default class MarkdownEditorView extends Component {
|
|||||||
// Component functions
|
// Component functions
|
||||||
_changeText = input => {
|
_changeText = input => {
|
||||||
const { onChange, handleOnTextChange, handleIsValid, componentID } = this.props;
|
const { onChange, handleOnTextChange, handleIsValid, componentID } = this.props;
|
||||||
|
const { textUpdated } = this.state;
|
||||||
|
|
||||||
|
if (textUpdated) {
|
||||||
|
this.setState({
|
||||||
|
textUpdated: false,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({ text: input });
|
this.setState({ text: input });
|
||||||
|
|
||||||
@ -100,14 +113,16 @@ export default class MarkdownEditorView extends Component {
|
|||||||
selection: newSelection,
|
selection: newSelection,
|
||||||
newSelection: null,
|
newSelection: null,
|
||||||
});
|
});
|
||||||
} else {
|
return;
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
selection: event.nativeEvent.selection,
|
selection: event.nativeEvent.selection,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_getState = () => this.state;
|
_getState = () => {
|
||||||
|
return this.state;
|
||||||
|
};
|
||||||
|
|
||||||
_renderPreview = () => {
|
_renderPreview = () => {
|
||||||
const { text } = this.state;
|
const { text } = this.state;
|
||||||
@ -154,7 +169,7 @@ export default class MarkdownEditorView extends Component {
|
|||||||
onPress={() => Formats[9].onPress({ getState, setState })}
|
onPress={() => Formats[9].onPress({ getState, setState })}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
onPress={() => this.ActionSheet.show()}
|
onPress={() => this.galleryRef.current.show()}
|
||||||
style={styles.rightIcons}
|
style={styles.rightIcons}
|
||||||
size={20}
|
size={20}
|
||||||
iconStyle={styles.icon}
|
iconStyle={styles.icon}
|
||||||
@ -163,21 +178,14 @@ export default class MarkdownEditorView extends Component {
|
|||||||
/>
|
/>
|
||||||
<View style={styles.clearButtonWrapper}>
|
<View style={styles.clearButtonWrapper}>
|
||||||
<IconButton
|
<IconButton
|
||||||
onPress={() => this.ClearActionSheet.show()}
|
onPress={() => this.clearRef.current.show()}
|
||||||
size={20}
|
size={20}
|
||||||
iconStyle={styles.clearIcon}
|
iconStyle={styles.clearIcon}
|
||||||
iconType="FontAwesome"
|
iconType="FontAwesome"
|
||||||
name="trash"
|
name="trash"
|
||||||
|
backgroundColor={styles.clearButtonWrapper.backgroundColor}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{/* TODO: After alpha */}
|
|
||||||
{/* <DropdownButton
|
|
||||||
style={styles.dropdownStyle}
|
|
||||||
options={['option1', 'option2', 'option3', 'option4']}
|
|
||||||
iconName="md-more"
|
|
||||||
iconStyle={styles.dropdownIconStyle}
|
|
||||||
isHasChildIcon
|
|
||||||
/> */}
|
|
||||||
</View>
|
</View>
|
||||||
</StickyBar>
|
</StickyBar>
|
||||||
);
|
);
|
||||||
@ -203,7 +211,7 @@ export default class MarkdownEditorView extends Component {
|
|||||||
{!isPreviewActive ? (
|
{!isPreviewActive ? (
|
||||||
<TextInput
|
<TextInput
|
||||||
multiline
|
multiline
|
||||||
onChangeText={e => this._changeText(e)}
|
onChangeText={this._changeText}
|
||||||
onSelectionChange={this._handleOnSelectionChange}
|
onSelectionChange={this._handleOnSelectionChange}
|
||||||
placeholder={intl.formatMessage({
|
placeholder={intl.formatMessage({
|
||||||
id: isReply ? 'editor.reply_placeholder' : 'editor.default_placeholder',
|
id: isReply ? 'editor.reply_placeholder' : 'editor.default_placeholder',
|
||||||
@ -214,6 +222,7 @@ export default class MarkdownEditorView extends Component {
|
|||||||
style={styles.textWrapper}
|
style={styles.textWrapper}
|
||||||
underlineColorAndroid="transparent"
|
underlineColorAndroid="transparent"
|
||||||
value={text}
|
value={text}
|
||||||
|
innerRef={this.inputRef}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
this._renderPreview()
|
this._renderPreview()
|
||||||
@ -222,13 +231,12 @@ export default class MarkdownEditorView extends Component {
|
|||||||
this._renderEditorButtons({
|
this._renderEditorButtons({
|
||||||
getState: this._getState,
|
getState: this._getState,
|
||||||
setState: (state, callback) => {
|
setState: (state, callback) => {
|
||||||
|
this.inputRef.current.focus();
|
||||||
this.setState(state, callback);
|
this.setState(state, callback);
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{/* TODO: This is a problem re-factor */}
|
|
||||||
<ActionSheet
|
<ActionSheet
|
||||||
ref={o => (this.ActionSheet = o)}
|
ref={this.galleryRef}
|
||||||
options={[
|
options={[
|
||||||
intl.formatMessage({
|
intl.formatMessage({
|
||||||
id: 'editor.open_gallery',
|
id: 'editor.open_gallery',
|
||||||
@ -246,7 +254,7 @@ export default class MarkdownEditorView extends Component {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ActionSheet
|
<ActionSheet
|
||||||
ref={o => (this.ClearActionSheet = o)}
|
ref={this.clearRef}
|
||||||
title={intl.formatMessage({
|
title={intl.formatMessage({
|
||||||
id: 'alert.clear_alert',
|
id: 'alert.clear_alert',
|
||||||
})}
|
})}
|
||||||
@ -259,9 +267,7 @@ export default class MarkdownEditorView extends Component {
|
|||||||
}),
|
}),
|
||||||
]}
|
]}
|
||||||
cancelButtonIndex={1}
|
cancelButtonIndex={1}
|
||||||
onPress={index => {
|
onPress={index => index === 0 && this._handleClear()}
|
||||||
index === 0 && this._handleClear();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
);
|
);
|
||||||
|
@ -120,10 +120,10 @@ class PostCardView extends Component {
|
|||||||
<Text style={styles.comment}>{content.vote_count}</Text>
|
<Text style={styles.comment}>{content.vote_count}</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
<TouchableOpacity style={styles.commentButton}>
|
<View style={styles.commentButton}>
|
||||||
<Icon style={[styles.commentIcon]} iconType="MaterialIcons" name="comment" />
|
<Icon style={[styles.commentIcon]} iconType="MaterialIcons" name="comment" />
|
||||||
<Text style={styles.comment}>{content.children}</Text>
|
<Text style={styles.comment}>{content.children}</Text>
|
||||||
</TouchableOpacity>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import React, { PureComponent, Fragment } from 'react';
|
import React, { PureComponent, Fragment } from 'react';
|
||||||
import {
|
import { Dimensions, Linking, Alert, TouchableOpacity, Text } from 'react-native';
|
||||||
Dimensions, Linking, Alert, TouchableOpacity, Text,
|
|
||||||
} from 'react-native';
|
|
||||||
import { withNavigation } from 'react-navigation';
|
import { withNavigation } from 'react-navigation';
|
||||||
import { injectIntl } from 'react-intl';
|
import { injectIntl } from 'react-intl';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
|
import { proxifyImageSrc } from '@esteemapp/esteem-render-helpers';
|
||||||
|
|
||||||
import HTML from 'react-native-render-html';
|
import HTML from 'react-native-render-html';
|
||||||
import { getParentsTagsRecursively } from 'react-native-render-html/src/HTMLUtils';
|
import { getParentsTagsRecursively } from 'react-native-render-html/src/HTMLUtils';
|
||||||
@ -48,7 +47,7 @@ class PostBody extends PureComponent {
|
|||||||
if (!url) return;
|
if (!url) return;
|
||||||
const { intl } = this.props;
|
const { intl } = this.props;
|
||||||
|
|
||||||
Linking.canOpenURL(url).then((supported) => {
|
Linking.canOpenURL(url).then(supported => {
|
||||||
if (supported) {
|
if (supported) {
|
||||||
Linking.openURL(url);
|
Linking.openURL(url);
|
||||||
} else {
|
} else {
|
||||||
@ -148,7 +147,7 @@ class PostBody extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { body, isComment } = this.props;
|
const { body, isComment, textSelectable = true } = this.props;
|
||||||
const _initialDimensions = isComment
|
const _initialDimensions = isComment
|
||||||
? { width: WIDTH - 50, height: 80 }
|
? { width: WIDTH - 50, height: 80 }
|
||||||
: { width: WIDTH, height: 216 };
|
: { width: WIDTH, height: 216 };
|
||||||
@ -158,7 +157,10 @@ class PostBody extends PureComponent {
|
|||||||
<FastImage
|
<FastImage
|
||||||
key={passProps.key}
|
key={passProps.key}
|
||||||
defaultSource={DEFAULT_IMAGE}
|
defaultSource={DEFAULT_IMAGE}
|
||||||
source={{ uri: htmlAttribs.src, priority: FastImage.priority.normal }}
|
source={{
|
||||||
|
uri: proxifyImageSrc(htmlAttribs.src, _initialDimensions.width, 0),
|
||||||
|
priority: FastImage.priority.normal,
|
||||||
|
}}
|
||||||
style={isComment ? styles.commentImage : styles.postImage}
|
style={isComment ? styles.commentImage : styles.postImage}
|
||||||
resizeMode={FastImage.resizeMode.contain}
|
resizeMode={FastImage.resizeMode.contain}
|
||||||
/>
|
/>
|
||||||
@ -166,13 +168,21 @@ class PostBody extends PureComponent {
|
|||||||
a: (htmlAttribs, children, convertedCSSStyles, passProps) => {
|
a: (htmlAttribs, children, convertedCSSStyles, passProps) => {
|
||||||
if (passProps.parentWrapper === 'Text') {
|
if (passProps.parentWrapper === 'Text') {
|
||||||
return (
|
return (
|
||||||
<Text key={passProps.key} {...htmlAttribs} onPress={() => this._handleOnLinkPress(htmlAttribs['data-href'], htmlAttribs)}>
|
<Text
|
||||||
|
key={passProps.key}
|
||||||
|
{...htmlAttribs}
|
||||||
|
onPress={() => this._handleOnLinkPress(htmlAttribs['data-href'], htmlAttribs)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity key={passProps.key} {...htmlAttribs} onPress={() => this._handleOnLinkPress(htmlAttribs['data-href'], htmlAttribs)}>
|
<TouchableOpacity
|
||||||
|
key={passProps.key}
|
||||||
|
{...htmlAttribs}
|
||||||
|
onPress={() => this._handleOnLinkPress(htmlAttribs['data-href'], htmlAttribs)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
);
|
);
|
||||||
@ -185,7 +195,7 @@ class PostBody extends PureComponent {
|
|||||||
html={body}
|
html={body}
|
||||||
onLinkPress={(evt, href, hrefatr) => this._handleOnLinkPress(evt, href, hrefatr)}
|
onLinkPress={(evt, href, hrefatr) => this._handleOnLinkPress(evt, href, hrefatr)}
|
||||||
containerStyle={isComment ? styles.commentContainer : styles.container}
|
containerStyle={isComment ? styles.commentContainer : styles.container}
|
||||||
textSelectable
|
textSelectable={textSelectable}
|
||||||
tagsStyles={isComment ? { img: { height: 120 } } : styles}
|
tagsStyles={isComment ? { img: { height: 120 } } : styles}
|
||||||
ignoredTags={['script']}
|
ignoredTags={['script']}
|
||||||
debug={false}
|
debug={false}
|
||||||
|
@ -185,8 +185,8 @@ class PostDisplayView extends PureComponent {
|
|||||||
<View style={styles.footer}>
|
<View style={styles.footer}>
|
||||||
<Tags tags={post.json_metadata && post.json_metadata.tags} />
|
<Tags tags={post.json_metadata && post.json_metadata.tags} />
|
||||||
<Text style={styles.footerText}>
|
<Text style={styles.footerText}>
|
||||||
Posted by <Text style={styles.footerName}>{author || post.author}</Text>
|
Posted by
|
||||||
{' '}
|
<Text style={styles.footerName}>{author || post.author}</Text>
|
||||||
{formatedTime}
|
{formatedTime}
|
||||||
</Text>
|
</Text>
|
||||||
{/* {isPostEnd && this._getTabBar()} */}
|
{/* {isPostEnd && this._getTabBar()} */}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { TextInput } from 'react-native';
|
import { TextInput } from 'react-native';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
const TextInputView = props => (
|
const TextInputView = ({ isDarkTheme, innerRef, ...props }) => (
|
||||||
<TextInput keyboardAppearance={props.isDarkTheme ? 'dark' : 'light'} {...props} />
|
<TextInput ref={innerRef} keyboardAppearance={isDarkTheme ? 'dark' : 'light'} {...props} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
|
@ -199,7 +199,8 @@
|
|||||||
},
|
},
|
||||||
"post": {
|
"post": {
|
||||||
"reblog_alert": "Are you sure you want to reblog?",
|
"reblog_alert": "Are you sure you want to reblog?",
|
||||||
"removed_hint": "The post was removed by"
|
"removed_hint": "The post was removed by",
|
||||||
|
"copy_link": "Copy Link"
|
||||||
},
|
},
|
||||||
"drafts": {
|
"drafts": {
|
||||||
"title": "Drafts",
|
"title": "Drafts",
|
||||||
|
@ -3,9 +3,9 @@ import { injectIntl } from 'react-intl';
|
|||||||
import { View, FlatList, Text } from 'react-native';
|
import { View, FlatList, Text } from 'react-native';
|
||||||
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
import ScrollableTabView from 'react-native-scrollable-tab-view';
|
||||||
import ActionSheet from 'react-native-actionsheet';
|
import ActionSheet from 'react-native-actionsheet';
|
||||||
|
import { postBodySummary } from '@esteemapp/esteem-render-helpers';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { getPostSummary } from '../../../utils/formatter';
|
|
||||||
import { catchDraftImage } from '../../../utils/image';
|
import { catchDraftImage } from '../../../utils/image';
|
||||||
import { getFormatedCreatedDate } from '../../../utils/time';
|
import { getFormatedCreatedDate } from '../../../utils/time';
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ class DraftsScreen extends Component {
|
|||||||
const tags = item.tags ? item.tags.split(/[ ,]+/) : [];
|
const tags = item.tags ? item.tags.split(/[ ,]+/) : [];
|
||||||
const tag = tags[0] || '';
|
const tag = tags[0] || '';
|
||||||
const image = catchDraftImage(item.body);
|
const image = catchDraftImage(item.body);
|
||||||
const summary = getPostSummary(item.body, 100);
|
const summary = postBodySummary(item, 100);
|
||||||
const isSchedules = type === 'schedules';
|
const isSchedules = type === 'schedules';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable no-restricted-globals */
|
|
||||||
import React, { Fragment, Component } from 'react';
|
import React, { Fragment, Component } from 'react';
|
||||||
import { Text, View, WebView, ScrollView, TouchableOpacity } from 'react-native';
|
import { Text, View, WebView, ScrollView, TouchableOpacity } from 'react-native';
|
||||||
import ActionSheet from 'react-native-actionsheet';
|
import ActionSheet from 'react-native-actionsheet';
|
||||||
|
@ -1,22 +1,3 @@
|
|||||||
export const getPostSummary = (postBody, length, isQuote) => {
|
|
||||||
if (!postBody) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
postBody = postBody
|
|
||||||
.replace(/(<([^>]+)>)/gi, '') // Remove html tags
|
|
||||||
.replace(/\r?\n|\r/g, ' ') // Remove new lines
|
|
||||||
.replace(/(?:https?|ftp):\/\/[\n\S]+/g, '') // Remove urls
|
|
||||||
.trim()
|
|
||||||
.replace(/ +(?= )/g, ''); // Remove all multiple spaces
|
|
||||||
|
|
||||||
if (length) {
|
|
||||||
// Truncate
|
|
||||||
postBody = postBody.substring(0, length);
|
|
||||||
}
|
|
||||||
return isQuote ? `"${postBody}..."` : `${postBody}...`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const makeCountFriendly = value => {
|
export const makeCountFriendly = value => {
|
||||||
if (!value) return value;
|
if (!value) return value;
|
||||||
if (value >= 1000000) return `${intlFormat(value / 1000000)}M`;
|
if (value >= 1000000) return `${intlFormat(value / 1000000)}M`;
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
// TODO: Refactor
|
|
||||||
export const sanitizeNode = node => {
|
|
||||||
const ALLOWED_TAGS = [
|
|
||||||
'A',
|
|
||||||
'STRONG',
|
|
||||||
'B',
|
|
||||||
'I',
|
|
||||||
'EM',
|
|
||||||
'CODE',
|
|
||||||
'BLOCKQUOTE',
|
|
||||||
'SUP',
|
|
||||||
'SUB',
|
|
||||||
'H2',
|
|
||||||
'H1',
|
|
||||||
'H3',
|
|
||||||
'H4',
|
|
||||||
'H5',
|
|
||||||
'H6',
|
|
||||||
'DIV',
|
|
||||||
'P',
|
|
||||||
'IFRAME',
|
|
||||||
'CENTER',
|
|
||||||
'UL',
|
|
||||||
'OL',
|
|
||||||
'LI',
|
|
||||||
'TABLE',
|
|
||||||
'THEAD',
|
|
||||||
'TBODY',
|
|
||||||
'TR',
|
|
||||||
'TD',
|
|
||||||
'TH',
|
|
||||||
'HR',
|
|
||||||
'BR',
|
|
||||||
'IMG',
|
|
||||||
];
|
|
||||||
|
|
||||||
const ALLOWED_ATTRS = [
|
|
||||||
'data-permlink',
|
|
||||||
'data-tag',
|
|
||||||
'data-author',
|
|
||||||
'data-href',
|
|
||||||
'class',
|
|
||||||
'src',
|
|
||||||
'alt',
|
|
||||||
'title',
|
|
||||||
'width',
|
|
||||||
'height',
|
|
||||||
'border',
|
|
||||||
'frameborder',
|
|
||||||
'allowfullscreen',
|
|
||||||
'mozallowfullscreen',
|
|
||||||
'webkitallowfullscreen',
|
|
||||||
];
|
|
||||||
|
|
||||||
const allElems = node.querySelectorAll('*');
|
|
||||||
allElems.forEach(el => {
|
|
||||||
if (ALLOWED_TAGS.indexOf(el.tagName) === -1) {
|
|
||||||
el.outerHTML = `<span>${el.innerText.replace('>', '>').replace('<', '<')}</span>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const attr of el.attributes) {
|
|
||||||
if (ALLOWED_ATTRS.indexOf(attr.name) === -1) {
|
|
||||||
el.removeAttribute(attr.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return node;
|
|
||||||
};
|
|
@ -1,33 +1,22 @@
|
|||||||
import CryptoJS from 'crypto-js';
|
import CryptoJS from 'crypto-js';
|
||||||
import * as dsteem from 'dsteem';
|
import * as dsteem from 'dsteem';
|
||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
|
import { proxifyImageSrc } from '@esteemapp/esteem-render-helpers';
|
||||||
|
|
||||||
export const generateSignature = (media, privateKey) => {
|
export const generateSignature = (media, privateKey) => {
|
||||||
const STRING = 'ImageSigningChallenge';
|
const STRING = 'ImageSigningChallenge';
|
||||||
const prefix = new Buffer(STRING);
|
const prefix = Buffer.from(STRING);
|
||||||
|
|
||||||
const commaIdx = media.data.indexOf(',');
|
const commaIdx = media.data.indexOf(',');
|
||||||
const dataBs64 = media.data.substring(commaIdx + 1);
|
const dataBs64 = media.data.substring(commaIdx + 1);
|
||||||
const data = new Buffer(dataBs64, 'base64');
|
const data = Buffer.from(dataBs64, 'base64');
|
||||||
|
|
||||||
const hash = CryptoJS.SHA256(prefix, data);
|
const hash = CryptoJS.SHA256(prefix, data);
|
||||||
const buffer = Buffer.from(hash.toString(CryptoJS.enc.Hex), 'hex');
|
const buffer = Buffer.from(hash.toString(CryptoJS.enc.Hex), 'hex');
|
||||||
const array = new Uint8Array(buffer);
|
const array = new Uint8Array(buffer);
|
||||||
const key = dsteem.PrivateKey.fromString(privateKey);
|
const key = dsteem.PrivateKey.fromString(privateKey);
|
||||||
|
|
||||||
return key.sign(new Buffer(array)).toString();
|
return key.sign(Buffer.from(array)).toString();
|
||||||
};
|
|
||||||
|
|
||||||
export const proxifyImageSrc = (url, width = 0, height = 0) => {
|
|
||||||
if (!url) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const prefix = `https://steemitimages.com/${width}x${height}/`;
|
|
||||||
|
|
||||||
if (url.startsWith(prefix)) return url;
|
|
||||||
|
|
||||||
return `${prefix}${url}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const catchEntryImage = (entry, width = 0, height = 0) => {
|
export const catchEntryImage = (entry, width = 0, height = 0) => {
|
||||||
|
@ -1,342 +0,0 @@
|
|||||||
import Remarkable from 'remarkable';
|
|
||||||
// TODO: Refactoring need!
|
|
||||||
const md = new Remarkable({ html: true, breaks: true, linkify: true });
|
|
||||||
|
|
||||||
// const imgCenterRegex = /([<center>]http(s?):)([/|.|\w|\s|-])*\.(?:jpg|gif|png|PNG|GIF|JPG)[</center>]/g;
|
|
||||||
// const onlyImageLinkRegex = /([\n]http(s?):)([/|.|\w|\s|-])*\.(?:jpg|gif|png|PNG|GIF|JPG)/g;
|
|
||||||
// const onlyImageDoubleLinkRegex = /(\nhttps)(.*)(?=jpg|gif|png|PNG|GIF|JPG|)/g;
|
|
||||||
// const pullRightLeftRegex = /(<div class="[^"]*?pull-[^"]*?">(.*?)(<[/]div>))/g;
|
|
||||||
// const markdownLinkRegex = /(?:__|[])|\[(.*?)\]\(.*?\)/g;
|
|
||||||
const copiedPostRegex = /\/(.*)\/(@[\w.\d-]+)\/(.*)/i;
|
|
||||||
const postRegex = /^https?:\/\/(.*)\/(.*)\/(@[\w.\d-]+)\/(.*)/i;
|
|
||||||
const youTubeRegex = /(?:https?:\/\/)?(?:www\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([^& \n<]+)(?:[^ \n<]+)?/g;
|
|
||||||
const vimeoRegex = /(https?:\/\/)?(www\.)?(?:vimeo)\.com.*(?:videos|video|channels|)\/([\d]+)/i;
|
|
||||||
const dTubeRegex = /(https?:\/\/d.tube.#!\/v\/)(\w+)\/(\w+)/g;
|
|
||||||
const authorNameRegex = /(^|[^a-zA-Z0-9_!#$%&*@@\/]|(^|[^a-zA-Z0-9_+~.-\/]))[@@]([a-z][-\.a-z\d]+[a-z\d])/gi;
|
|
||||||
const tagsRegex = /(^|\s|>)(#[-a-z\d]+)/gi;
|
|
||||||
const centerRegex = /(<center>)/g;
|
|
||||||
const imgRegex = /(https?:\/\/.*\.(?:tiff?|jpe?g|gif|png|svg|ico|PNG|GIF|JPG|JPEG))/g;
|
|
||||||
const linkRegex = /[-a-zA-Z0-9@:%+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%+._~#?&//=]*)?/gi;
|
|
||||||
const markdownImageRegex = /!\[[^\]]*\]\((.*?)\s*("(?:.*[^"])")?\s*\)/g;
|
|
||||||
const urlRegex = /(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/gm;
|
|
||||||
const aTagRegex = /(<\s*a[^>]*>(.*?)<\s*[/]\s*a>)/g;
|
|
||||||
const imgTagRegex = /(<img[^>]*>)/g;
|
|
||||||
const iframeRegex = /(?:<iframe[^>]*)(?:(?:\/>)|(?:>.*?<\/iframe>))/g;
|
|
||||||
const hTagRegex = /(<h([1-6])>([^<]*)<\/h([1-6])>)/g;
|
|
||||||
const emptyLinkRegex = /!\[]\(\)/g;
|
|
||||||
|
|
||||||
export const markDown2Html = input => {
|
|
||||||
if (!input) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
let output = input;
|
|
||||||
|
|
||||||
if (authorNameRegex.test(output)) {
|
|
||||||
output = replaceAuthorNames(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (emptyLinkRegex.test(output)) {
|
|
||||||
output = handleEmptyLink(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tagsRegex.test(output)) {
|
|
||||||
output = replaceTags(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (youTubeRegex.test(output)) {
|
|
||||||
output = createYoutubeIframe(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imgTagRegex.test(output)) {
|
|
||||||
output = handleImageTag(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vimeoRegex.test(output)) {
|
|
||||||
output = createVimeoIframe(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vimeoRegex.test(output)) {
|
|
||||||
output = createVimeoIframe(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (centerRegex.test(output)) {
|
|
||||||
output = centerStyling(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (postRegex.test(output)) {
|
|
||||||
output = steemitUrlHandle(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (markdownImageRegex.test(output)) {
|
|
||||||
output = changeMarkdownImage(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (iframeRegex.test(output)) {
|
|
||||||
output = handleIframe(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (imgRegex.test(output)) {
|
|
||||||
// output = handleImageLink(output);
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (linkRegex.test(output)) {
|
|
||||||
output = handleLinks(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aTagRegex.test(output)) {
|
|
||||||
output = handleATag(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hTagRegex.test(output)) {
|
|
||||||
output = handleHTag(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (copiedPostRegex.test(output)) {
|
|
||||||
// output = handleMarkdownLink(output);
|
|
||||||
// }
|
|
||||||
|
|
||||||
output = md.render(output);
|
|
||||||
|
|
||||||
return output;
|
|
||||||
};
|
|
||||||
|
|
||||||
const replaceAuthorNames = input =>
|
|
||||||
input.replace(authorNameRegex, (match, preceeding1, preceeding2, user) => {
|
|
||||||
const userLower = user.toLowerCase();
|
|
||||||
const preceedings = (preceeding1 || '') + (preceeding2 || '');
|
|
||||||
return `${preceedings}<a class="markdown-author-link" href="${userLower}" data-author="${userLower}">@${user}</a>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const replaceTags = input =>
|
|
||||||
input.replace(tagsRegex, tag => {
|
|
||||||
if (/#[\d]+$/.test(tag)) return tag;
|
|
||||||
const preceding = /^\s|>/.test(tag) ? tag[0] : '';
|
|
||||||
tag = tag.replace('>', '');
|
|
||||||
const tag2 = tag.trim().substring(1);
|
|
||||||
const tagLower = tag2.toLowerCase();
|
|
||||||
return `${preceding}<a class="markdown-tag-link" href="${tagLower}" data-tag="${tagLower}">${tag.trim()}</a>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleATag = input =>
|
|
||||||
input.replace(aTagRegex, link => {
|
|
||||||
if (dTubeRegex.test(link)) {
|
|
||||||
const dTubeMatch = link.match(dTubeRegex)[0];
|
|
||||||
const execLink = dTubeRegex.exec(dTubeMatch);
|
|
||||||
|
|
||||||
if (execLink[2] && execLink[3]) {
|
|
||||||
const embedLink = `https://emb.d.tube/#!/${execLink[2]}/${execLink[3]}`;
|
|
||||||
|
|
||||||
return iframeBody(embedLink);
|
|
||||||
}
|
|
||||||
if (dTubeMatch) {
|
|
||||||
return iframeBody(dTubeMatch);
|
|
||||||
}
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imgRegex.test(link)) {
|
|
||||||
const imgMatch = link.match(imgRegex)[0];
|
|
||||||
|
|
||||||
if (imgMatch) return `<a src="${imgMatch}">Image</a>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return link;
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleHTag = input => input.replace(hTagRegex, tag => `<div>${tag}</div>`);
|
|
||||||
|
|
||||||
const handleMarkdownLink = input =>
|
|
||||||
input.replace(copiedPostRegex, link => {
|
|
||||||
const postMatch = link.match(copiedPostRegex);
|
|
||||||
|
|
||||||
if (postMatch) {
|
|
||||||
let tag = postMatch[1];
|
|
||||||
|
|
||||||
if (tag === '/busy.org') {
|
|
||||||
tag = 'busy';
|
|
||||||
}
|
|
||||||
|
|
||||||
const _permlink =
|
|
||||||
postMatch[3].indexOf(')') > 0 ? postMatch[3].replace(')', '') : postMatch[3];
|
|
||||||
|
|
||||||
return `<a class="markdown-post-link" href="${_permlink}" data_tag={${tag.trim()}} data_author="${postMatch[2].replace(
|
|
||||||
'@',
|
|
||||||
'',
|
|
||||||
)}">/${_permlink}</a>`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleLinks = input =>
|
|
||||||
input.replace(linkRegex, link => {
|
|
||||||
if (link) {
|
|
||||||
if (
|
|
||||||
link
|
|
||||||
.toLowerCase()
|
|
||||||
.trim()
|
|
||||||
.indexOf('https://steemitimages.com/0x0/') === 0 ||
|
|
||||||
imgRegex.test(link)
|
|
||||||
) {
|
|
||||||
const imageMatch = link.match(imgRegex);
|
|
||||||
|
|
||||||
if (imageMatch) {
|
|
||||||
if (imageMatch[0].indexOf('.gif') > 0) {
|
|
||||||
return gifBody(imageMatch[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imageMatch[0]) {
|
|
||||||
return imageBody(imageMatch[0]);
|
|
||||||
}
|
|
||||||
} else if (link.trim().indexOf('ipfs.busy.org') > 0) {
|
|
||||||
return imageBody(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
if (link.trim().indexOf('ipfs.busy.org') > 0) {
|
|
||||||
return imageBody(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (imgRegex.test(link)) {
|
|
||||||
return imageBody(link);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return link;
|
|
||||||
});
|
|
||||||
|
|
||||||
const changeMarkdownImage = input =>
|
|
||||||
input.replace(markdownImageRegex, link => {
|
|
||||||
const markdownMatch = link.match(markdownImageRegex);
|
|
||||||
if (markdownMatch[0]) {
|
|
||||||
const firstMarkdownMatch = markdownMatch[0];
|
|
||||||
const _link = firstMarkdownMatch.match(urlRegex) && firstMarkdownMatch.match(urlRegex)[0];
|
|
||||||
|
|
||||||
if (_link) return _link;
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
return link;
|
|
||||||
});
|
|
||||||
|
|
||||||
const centerStyling = input =>
|
|
||||||
input.replace(
|
|
||||||
centerRegex,
|
|
||||||
() => '<center style="text-align: center; align-items: center; justify-content: center;">',
|
|
||||||
);
|
|
||||||
|
|
||||||
const steemitUrlHandle = input =>
|
|
||||||
input.replace(postRegex, link => {
|
|
||||||
const postMatch = link.match(postRegex);
|
|
||||||
const tag = postMatch[2];
|
|
||||||
const author = postMatch[3].replace('@', '');
|
|
||||||
const permlink = postMatch[4];
|
|
||||||
|
|
||||||
return `<a class="markdown-post-link" href="${permlink}" data_tag={${tag}} data_author="${author}">/${permlink}</a>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleImageTag = input =>
|
|
||||||
input.replace(imgTagRegex, imgTag => {
|
|
||||||
const _imgTag = imgTag.trim();
|
|
||||||
const match = _imgTag.match(imgRegex);
|
|
||||||
|
|
||||||
if (match && match[0]) {
|
|
||||||
return match[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return imgTag;
|
|
||||||
});
|
|
||||||
|
|
||||||
const createYoutubeIframe = input =>
|
|
||||||
input.replace(youTubeRegex, link => {
|
|
||||||
if (link.indexOf(')') || link.indexOf('(')) {
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
const execVideo = youTubeRegex.exec(link);
|
|
||||||
const match = link.match(youTubeRegex);
|
|
||||||
|
|
||||||
if (execVideo[1] && match) {
|
|
||||||
const videoLink = execVideo[1];
|
|
||||||
const embedLink = `https://www.youtube.com/embed/${videoLink}`;
|
|
||||||
|
|
||||||
return iframeBody(embedLink);
|
|
||||||
}
|
|
||||||
|
|
||||||
return link;
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleIframe = input =>
|
|
||||||
input.replace(iframeRegex, link => {
|
|
||||||
const match = link.match(linkRegex);
|
|
||||||
|
|
||||||
if (match && match[0]) {
|
|
||||||
return iframeBody(match[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return link;
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleEmptyLink = input => input.replace(handleEmptyLink, () => '');
|
|
||||||
|
|
||||||
const createVimeoIframe = input =>
|
|
||||||
input.replace(vimeoRegex, link => {
|
|
||||||
const execLink = vimeoRegex.exec(link);
|
|
||||||
|
|
||||||
const embedLink = `https://player.vimeo.com/video/${execLink[3]}`;
|
|
||||||
|
|
||||||
return iframeBody(embedLink);
|
|
||||||
});
|
|
||||||
|
|
||||||
const iframeBody = link => `<iframe frameborder='0' allowfullscreen src='${link}'></iframe>`;
|
|
||||||
const imageBody = link =>
|
|
||||||
`<center style="text-align: center; align-items: center; justify-content: center;"><img src="${`https://steemitimages.com/600x0/${link}`}" /></center>`;
|
|
||||||
const gifBody = link => `<img src="${`https://steemitimages.com/0x0/${link}`}" />`;
|
|
||||||
// const handleImageLink = input => input.replace(imgRegex, link => imageBody(link));
|
|
||||||
|
|
||||||
// const handleCodeTag = input => input.replace(codeTagRegex, (tag) => {
|
|
||||||
// const stringsRegex = /(?<=>)(.*)(?=<)/g;
|
|
||||||
// const match = tag.match(stringsRegex);
|
|
||||||
|
|
||||||
// if (match && match[0]) {
|
|
||||||
// return `<p class="code" >${match[0]}</p>`;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return iframeBody(match[0]);
|
|
||||||
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const createCenterImage = input => input.replace(imgCenterRegex, (link) => {
|
|
||||||
// let _link = link;
|
|
||||||
|
|
||||||
// _link = _link.split('>')[1];
|
|
||||||
// _link = _link.split('<')[0];
|
|
||||||
// return `><img data-href="${`https://steemitimages.com/600x0/${_link}`}" src="${`https://steemitimages.com/600x0/${_link}`}"><`;
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const changePullRightLeft = input => input.replace(pullRightLeftRegex, (item) => {
|
|
||||||
// const imageLink = item.match(linkRegex)[0];
|
|
||||||
|
|
||||||
// return `<center style="text-align:center;"><img src="${`https://steemitimages.com/600x0/${imageLink}`}"/></center><br>`;
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const createImage = input => input.replace(onlyImageLinkRegex, link => imageBody(link));
|
|
||||||
|
|
||||||
// const createFromDoubleImageLink = input => input.replace(onlyImageDoubleLinkRegex, (link) => {
|
|
||||||
// const _link = link.trim();
|
|
||||||
|
|
||||||
// return imageBody(_link);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const createDtubeIframe = input => input.replace(dTubeRegex, (link) => {
|
|
||||||
// const execLink = dTubeRegex.exec(link);
|
|
||||||
// const dTubeMatch = link.match(dTubeRegex)[0];
|
|
||||||
|
|
||||||
// if (execLink[2] && execLink[3]) {
|
|
||||||
// const embedLink = `https://emb.d.tube/#!/${execLink[2]}/${execLink[3]}`;
|
|
||||||
|
|
||||||
// return iframeBody(embedLink);
|
|
||||||
// }
|
|
||||||
// if (dTubeMatch) {
|
|
||||||
// return iframeBody(dTubeMatch);
|
|
||||||
// }
|
|
||||||
// return link;
|
|
||||||
// });
|
|
@ -1,18 +1,3 @@
|
|||||||
import parseToken from './parseToken';
|
|
||||||
import { isEmptyContentDate } from './time';
|
|
||||||
|
|
||||||
export const postSumTotal = content => {
|
|
||||||
if (content.pending_payout_value && isEmptyContentDate(content.last_payout)) {
|
|
||||||
return content.total_payout_value
|
|
||||||
? parseToken(content.total_payout_value) + parseToken(content.pending_payout_value)
|
|
||||||
: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return content.total_payout_value
|
|
||||||
? parseToken(content.total_payout_value) + parseToken(content.curator_payout_value)
|
|
||||||
: 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getPostUrl = url => {
|
export const getPostUrl = url => {
|
||||||
const BASE_URL = 'https://steemit.com';
|
const BASE_URL = 'https://steemit.com';
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import forEach from 'lodash/forEach';
|
||||||
import { postBodySummary, renderPostBody } from '@esteemapp/esteem-render-helpers';
|
import { postBodySummary, renderPostBody } from '@esteemapp/esteem-render-helpers';
|
||||||
// Utils
|
// Utils
|
||||||
import { markDown2Html } from './markdownToHtml';
|
|
||||||
import { getPostSummary } from './formatter';
|
|
||||||
import { getReputation } from './reputation';
|
import { getReputation } from './reputation';
|
||||||
|
|
||||||
export const parsePosts = (posts, currentUserName) =>
|
export const parsePosts = (posts, currentUserName) =>
|
||||||
@ -43,18 +43,15 @@ export const parsePost = (post, currentUserName) => {
|
|||||||
const voteRshares = post.active_votes.reduce((a, b) => a + parseFloat(b.rshares), 0);
|
const voteRshares = post.active_votes.reduce((a, b) => a + parseFloat(b.rshares), 0);
|
||||||
const ratio = totalPayout / voteRshares;
|
const ratio = totalPayout / voteRshares;
|
||||||
|
|
||||||
if (post.active_votes && post.active_votes.length > 0) {
|
if (!isEmpty(post.active_votes)) {
|
||||||
for (const i in post.active_votes) {
|
forEach(post.active_votes, value => {
|
||||||
post.vote_perecent =
|
post.vote_perecent = value.voter === currentUserName ? value.percent : null;
|
||||||
post.active_votes[i].voter === currentUserName ? post.active_votes[i].percent : null;
|
value.value = (value.rshares * ratio).toFixed(3);
|
||||||
post.active_votes[i].value = (post.active_votes[i].rshares * ratio).toFixed(3);
|
value.reputation = getReputation(value.reputation);
|
||||||
post.active_votes[i].reputation = getReputation(post.active_votes[i].reputation);
|
value.percent /= 100;
|
||||||
post.active_votes[i].percent = post.active_votes[i].percent / 100;
|
value.is_down_vote = Math.sign(value.percent) < 0;
|
||||||
post.active_votes[i].is_down_vote = Math.sign(post.active_votes[i].percent) < 0;
|
value.avatar = `https://steemitimages.com/u/${value.voter}/avatar/small`;
|
||||||
post.active_votes[i].avatar = `https://steemitimages.com/u/${
|
});
|
||||||
post.active_votes[i].voter
|
|
||||||
}/avatar/small`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return post;
|
return post;
|
||||||
@ -66,23 +63,24 @@ const isVoted = (activeVotes, currentUserName) =>
|
|||||||
const postImage = (metaData, body) => {
|
const postImage = (metaData, body) => {
|
||||||
const imgTagRegex = /(<img[^>]*>)/g;
|
const imgTagRegex = /(<img[^>]*>)/g;
|
||||||
const markdownImageRegex = /!\[[^\]]*\]\((.*?)\s*("(?:.*[^"])")?\s*\)/g;
|
const markdownImageRegex = /!\[[^\]]*\]\((.*?)\s*("(?:.*[^"])")?\s*\)/g;
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
const urlRegex = /(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/gm;
|
const urlRegex = /(http|ftp|https):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?/gm;
|
||||||
const imageRegex = /(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|gif|png)/g;
|
const imageRegex = /(http(s?):)([/|.|\w|\s|-])*\.(?:jpg|gif|png)/g;
|
||||||
let imageLink;
|
let imageLink;
|
||||||
|
|
||||||
if (metaData && metaData.image && metaData.image[0]) {
|
if (metaData && metaData.image && metaData.image[0]) {
|
||||||
imageLink = metaData.image[0];
|
[imageLink] = metaData.image;
|
||||||
} else if (body && markdownImageRegex.test(body)) {
|
} else if (body && markdownImageRegex.test(body)) {
|
||||||
const markdownMatch = body.match(markdownImageRegex);
|
const markdownMatch = body.match(markdownImageRegex);
|
||||||
if (markdownMatch[0]) {
|
if (markdownMatch[0]) {
|
||||||
const firstMarkdownMatch = markdownMatch[0];
|
const firstMarkdownMatch = markdownMatch[0];
|
||||||
imageLink = firstMarkdownMatch.match(urlRegex)[0];
|
[imageLink] = firstMarkdownMatch.match(urlRegex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!imageLink && imageRegex.test(body)) {
|
if (!imageLink && imageRegex.test(body)) {
|
||||||
const imageMatch = body.match(imageRegex);
|
const imageMatch = body.match(imageRegex);
|
||||||
imageLink = imageMatch[0];
|
[imageLink] = imageMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!imageLink && imgTagRegex.test(body)) {
|
if (!imageLink && imgTagRegex.test(body)) {
|
||||||
@ -90,7 +88,7 @@ const postImage = (metaData, body) => {
|
|||||||
const match = _imgTag[0].match(urlRegex);
|
const match = _imgTag[0].match(urlRegex);
|
||||||
|
|
||||||
if (match && match[0]) {
|
if (match && match[0]) {
|
||||||
imageLink = match[0];
|
[imageLink] = match;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,51 +98,15 @@ const postImage = (metaData, body) => {
|
|||||||
return '';
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
// export const protocolUrl2Obj = (url) => {
|
|
||||||
// let urlPart = url.split('://')[1];
|
|
||||||
|
|
||||||
// // remove last char if /
|
|
||||||
// if (urlPart.endsWith('/')) {
|
|
||||||
// urlPart = urlPart.substring(0, urlPart.length - 1);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const parts = urlPart.split('/');
|
|
||||||
|
|
||||||
// // filter
|
|
||||||
// if (parts.length === 1) {
|
|
||||||
// return { type: 'filter' };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // filter with tag
|
|
||||||
// if (parts.length === 2) {
|
|
||||||
// return { type: 'filter-tag', filter: parts[0], tag: parts[1] };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // account
|
|
||||||
// if (parts.length === 1 && parts[0].startsWith('@')) {
|
|
||||||
// return { type: 'account', account: parts[0].replace('@', '') };
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // post
|
|
||||||
// if (parts.length === 3 && parts[1].startsWith('@')) {
|
|
||||||
// return {
|
|
||||||
// type: 'post',
|
|
||||||
// cat: parts[0],
|
|
||||||
// author: parts[1].replace('@', ''),
|
|
||||||
// permlink: parts[2],
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const parseComments = comments => {
|
export const parseComments = comments => {
|
||||||
comments.map(comment => {
|
forEach(comments, comment => {
|
||||||
comment.pending_payout_value = parseFloat(comment.pending_payout_value).toFixed(3);
|
comment.pending_payout_value = parseFloat(comment.pending_payout_value).toFixed(3);
|
||||||
comment.vote_count = comment.active_votes.length;
|
comment.vote_count = comment.active_votes.length;
|
||||||
comment.author_reputation = getReputation(comment.author_reputation);
|
comment.author_reputation = getReputation(comment.author_reputation);
|
||||||
comment.avatar = `https://steemitimages.com/u/${comment.author}/avatar/small`;
|
comment.avatar = `https://steemitimages.com/u/${comment.author}/avatar/small`;
|
||||||
comment.markdownBody = comment.body;
|
comment.markdownBody = comment.body;
|
||||||
comment.body = renderPostBody(comment);
|
comment.body = renderPostBody(comment);
|
||||||
comment.summary = `"${getPostSummary(comment.body, 100, true)}"`;
|
comment.summary = `"${postBodySummary(comment, 100, true)}"`;
|
||||||
});
|
});
|
||||||
return comments;
|
return comments;
|
||||||
};
|
};
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
export default (url, width = 0, height = 0) => {
|
|
||||||
if (!url) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const prefix = `https://steemitimages.com/${width}x${height}/`;
|
|
||||||
|
|
||||||
if (url.startsWith(prefix)) return url;
|
|
||||||
|
|
||||||
return `${prefix}${url}`;
|
|
||||||
};
|
|
@ -1,15 +0,0 @@
|
|||||||
import { getUserData, getAuthStatus } from '../realm/realm';
|
|
||||||
|
|
||||||
export const getUserIsLoggedIn = () => {
|
|
||||||
getAuthStatus()
|
|
||||||
.then(res => res.isLoggedIn)
|
|
||||||
.catch(() => null);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getUserDataFromRealm = () => {
|
|
||||||
getUserData()
|
|
||||||
.then(res => {
|
|
||||||
userData = Array.from(res);
|
|
||||||
})
|
|
||||||
.catch(() => null);
|
|
||||||
};
|
|
@ -1,23 +1,24 @@
|
|||||||
export const getReputation = reputation => {
|
export const getReputation = reputation => {
|
||||||
if (reputation === null) return reputation;
|
if (reputation === null) return reputation;
|
||||||
|
|
||||||
let _reputation = String(parseInt(reputation));
|
let _reputation = String(parseInt(reputation, 10));
|
||||||
|
|
||||||
const neg = _reputation.charAt(0) === '-';
|
const neg = _reputation.charAt(0) === '-';
|
||||||
_reputation = neg ? _reputation.substring(1) : _reputation;
|
_reputation = neg ? _reputation.substring(1) : _reputation;
|
||||||
|
|
||||||
const str = _reputation;
|
const str = _reputation;
|
||||||
const leadingDigits = parseInt(str.substring(0, 4));
|
const leadingDigits = parseInt(str.substring(0, 4), 10);
|
||||||
const log = Math.log(leadingDigits) / Math.log(10);
|
const log = Math.log(leadingDigits) / Math.log(10);
|
||||||
const n = str.length - 1;
|
const n = str.length - 1;
|
||||||
let out = n + (log - parseInt(log));
|
let out = n + (log - parseInt(log, 10));
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-globals
|
||||||
if (isNaN(out)) out = 0;
|
if (isNaN(out)) out = 0;
|
||||||
|
|
||||||
out = Math.max(out - 9, 0);
|
out = Math.max(out - 9, 0);
|
||||||
out = (neg ? -1 : 1) * out;
|
out *= neg ? -1 : 1;
|
||||||
out = out * 9 + 25;
|
out = out * 9 + 25;
|
||||||
out = parseInt(out);
|
out = parseInt(out, 10);
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import qs from 'qs';
|
import qs from 'qs';
|
||||||
import { Linking } from 'react-native';
|
import { Linking } from 'react-native';
|
||||||
|
|
||||||
|
@ -22,17 +22,17 @@ export const getReputation = input => {
|
|||||||
|
|
||||||
return Math.floor(reputationLevel);
|
return Math.floor(reputationLevel);
|
||||||
};
|
};
|
||||||
/* eslint-disable */
|
|
||||||
export const getName = about => {
|
export const getName = about => {
|
||||||
if (about['profile'] && about['profile']['name']) {
|
if (about.profile && about.profile.name) {
|
||||||
return about['profile']['name'];
|
return about.profile.name;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAvatar = about => {
|
export const getAvatar = about => {
|
||||||
if (about['profile'] && about['profile']['profile_image']) {
|
if (about.profile && about.profile.profile_image) {
|
||||||
return about['profile']['profile_image'];
|
return about.profile.profile_image;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
@ -10,8 +10,7 @@ export const groomingTransactionData = (transaction, steemPerMVests, formatNumbe
|
|||||||
|
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
// eslint-disable-next-line
|
[result.opName] = transaction[1].op;
|
||||||
result.opName = transaction[1].op[0];
|
|
||||||
const opData = transaction[1].op[1];
|
const opData = transaction[1].op[1];
|
||||||
const { timestamp } = transaction[1];
|
const { timestamp } = transaction[1];
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user