Merge pull request #1312 from esteemapp/bugfix/post-display

Markdown refactoring
This commit is contained in:
Mustafa Buyukcelebi 2019-11-25 19:18:51 +03:00 committed by GitHub
commit 499abb49bd
9 changed files with 333 additions and 141 deletions

View File

@ -49,6 +49,7 @@
"react-native": "0.61.2", "react-native": "0.61.2",
"react-native-actionsheet": "^2.4.2", "react-native-actionsheet": "^2.4.2",
"react-native-autocomplete-input": "^4.1.0", "react-native-autocomplete-input": "^4.1.0",
"react-native-autoheight-webview": "^1.2.2",
"react-native-code-push": "esteemapp/react-native-code-push", "react-native-code-push": "esteemapp/react-native-code-push",
"react-native-config": "luggit/react-native-config#master", "react-native-config": "luggit/react-native-config#master",
"react-native-dark-mode": "^0.1.2", "react-native-dark-mode": "^0.1.2",

View File

@ -19,10 +19,12 @@ import WalletDetailsPlaceHolder from './view/placeHolder/walletDetailsPlaceHolde
import WalletUnclaimedPlaceHolder from './view/placeHolder/walletUnclaimedPlaceHolder'; import WalletUnclaimedPlaceHolder from './view/placeHolder/walletUnclaimedPlaceHolder';
import ListPlaceHolder from './view/placeHolder/listPlaceHolderView'; import ListPlaceHolder from './view/placeHolder/listPlaceHolderView';
import BoostPlaceHolder from './view/placeHolder/boostPlaceHolderView'; import BoostPlaceHolder from './view/placeHolder/boostPlaceHolderView';
import CommentPlaceHolder from './view/placeHolder/commentPlaceHolderView';
export { export {
Card, Card,
Chip, Chip,
CommentPlaceHolder,
GrayWrapper, GrayWrapper,
LineBreak, LineBreak,
ListItemPlaceHolder, ListItemPlaceHolder,

View File

@ -0,0 +1,35 @@
import React from 'react';
import { View } from 'react-native';
import Placeholder from 'rn-placeholder';
import { ThemeContainer } from '../../../../containers';
import styles from './listItemPlaceHolderStyles';
const CommentPlaceHolderView = () => {
return (
<ThemeContainer>
{({ isDarkTheme }) => {
const color = isDarkTheme ? '#2e3d51' : '#f5f5f5';
return (
<View style={styles.container}>
<View style={styles.paragraphWithoutMargin}>
<Placeholder.Paragraph
color={color}
lineNumber={3}
textSize={12}
lineSpacing={8}
width="100%"
lastLineWidth="70%"
firstLineWidth="50%"
animate="fade"
/>
</View>
</View>
);
}}
</ThemeContainer>
);
};
export default CommentPlaceHolderView;

View File

@ -16,4 +16,7 @@ export default EStyleSheet.create({
marginTop: 3, marginTop: 3,
flex: 1, flex: 1,
}, },
paragraphWithoutMargin: {
flex: 1,
},
}); });

View File

@ -0,0 +1,106 @@
export default `document.addEventListener('click', function(event) {
let el = event.target;
// A element can be wrapped with inline element. Look parent elements.
while (el.tagName !== 'A') {
if (!el.parentNode) {
break;
}
el = el.parentNode;
}
if (!el || el.tagName !== 'A') {
window.ReactNativeWebView.postMessage('4');
if (el.tagName) window.ReactNativeWebView.postMessage(el.tagName);
return;
}
if (el.getAttribute('target') === '_external') {
const href = el.getAttribute('href');
const result = {
type: '_external',
href
}
window.ReactNativeWebView.postMessage(JSON.stringify(result));
return true;
}
if (el.classList.contains('markdown-external-link')) {
const href = el.getAttribute('data-href');
const result = {
type: 'markdown-external-link',
href
}
window.ReactNativeWebView.postMessage(JSON.stringify(result));
return true;
}
if (el.classList.contains('markdown-author-link')) {
const author = el.getAttribute('data-author');
const result = {
type: 'markdown-author-link',
author,
}
window.ReactNativeWebView.postMessage(JSON.stringify(result));
return false;
}
if (el.classList.contains('markdown-post-link')) {
let category = el.getAttribute('data-tag');
let author = el.getAttribute('data-author');
let permlink = el.getAttribute('data-permlink');
const result = {
type: 'markdown-post-link',
category,
author,
permlink,
}
window.ReactNativeWebView.postMessage(JSON.stringify(result));
return false;
}
if (el.classList.contains('markdown-tag-link')) {
let tag = el.getAttribute('data-tag');
const result = {
type: 'markdown-tag-link',
tag
}
window.ReactNativeWebView.postMessage(JSON.stringify(result));
return false;
}
if (el.classList.contains('markdown-witnesses-link')) {
const result = {
type: 'markdown-witnesses-link'
}
window.ReactNativeWebView.postMessage(JSON.stringify(result));
return false;
}
if (el.classList.contains('markdown-proposal-link')) {
let proposal = el.getAttribute('data-proposal');
const result = {
type: 'markdown-proposal-link',
proposal
}
window.ReactNativeWebView.postMessage(JSON.stringify(result));
return false;
}
if (el.classList.contains('markdown-video-link')) {
const embedSrc = '<iframe frameborder="0" allowfullscreen src="' + el.getAttribute('data-embed-src') + '"></iframe>';
if (embedSrc) {
el.innerHTML = embedSrc;
return;
}
const videoHref = el.getAttribute('data-video-href');
if (videoHref) {
const result = {
type: 'markdown-video-link',
videoHref
}
window.ReactNativeWebView.postMessage(JSON.stringify(result));
return false;
}
}
window.ReactNativeWebView.postMessage('4');
const author = el.getAttribute('data-author').toString();
window.ReactNativeWebView.postMessage(JSON.stringify(author));
})
true;`;

View File

@ -1,16 +1,17 @@
import React from 'react'; import React, { Fragment } from 'react';
import { Dimensions, Linking, Alert, TouchableOpacity, Text } from 'react-native'; import { Dimensions, Linking, Alert } from 'react-native';
import { withNavigation } from 'react-navigation'; import { withNavigation } from 'react-navigation';
import { useIntl, injectIntl } from 'react-intl'; import { useIntl, injectIntl } from 'react-intl';
import HTML from 'react-native-render-html'; import AutoHeightWebView from 'react-native-autoheight-webview';
import { getParentsTagsRecursively } from 'react-native-render-html/src/HTMLUtils'; import EStyleSheet from 'react-native-extended-stylesheet';
import get from 'lodash/get';
import script from './config.js';
import { PostPlaceHolder, CommentPlaceHolder } from '../../../basicUIElements';
// Constants // Constants
import { default as ROUTES } from '../../../../constants/routeNames'; import { default as ROUTES } from '../../../../constants/routeNames';
// Styles
import styles from './postBodyStyles';
const WIDTH = Dimensions.get('window').width; const WIDTH = Dimensions.get('window').width;
const PostBody = ({ const PostBody = ({
@ -23,36 +24,72 @@ const PostBody = ({
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
const _handleOnLinkPress = (href, hrefAtr) => { const _handleOnLinkPress = event => {
if (hrefAtr.class === 'markdown-author-link') { if ((!event && !get(event, 'nativeEvent.data'), false)) {
if (!handleOnUserPress) { return;
_handleOnUserPress(hrefAtr['data-author']); }
} else {
handleOnUserPress(hrefAtr['data-author']); try {
const data = JSON.parse(get(event, 'nativeEvent.data'));
const { type, href, author, category, permlink, tag, proposal, videoHref } = data;
switch (type) {
case '_external':
case 'markdown-external-link':
_handleBrowserLink(href);
break;
case 'markdown-author-link':
if (!handleOnUserPress) {
_handleOnUserPress(author);
} else {
handleOnUserPress(author);
}
break;
case 'markdown-post-link':
if (!handleOnPostPress) {
_handleOnPostPress(permlink, author);
} else {
handleOnPostPress(permlink, author);
}
break;
case 'markdown-tag-link':
_handleTagPress(tag);
break;
case 'markdown-witnesses-link':
break;
case 'markdown-proposal-link':
break;
case 'markdown-video-link':
break;
default:
break;
} }
} else if (hrefAtr.class === 'markdown-post-link') { } catch (error) {}
if (!handleOnPostPress) { };
_handleOnPostPress(hrefAtr['data-permlink'], hrefAtr['data-author']);
} else { const _handleTagPress = tag => {
handleOnPostPress(hrefAtr['data-permlink']); if (tag) {
} navigation.navigate({
} else { routeName: ROUTES.SCREENS.SEARCH_RESULT,
_handleBrowserLink(href); params: {
tag,
},
});
} }
}; };
const _handleBrowserLink = async url => { const _handleBrowserLink = async url => {
if (!url) { if (url) {
return; Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
} else {
Alert.alert(intl.formatMessage({ id: 'alert.failed_to_open' }));
}
});
} }
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
} else {
Alert.alert(intl.formatMessage({ id: 'alert.failed_to_open' }));
}
});
}; };
const _handleOnPostPress = (permlink, author) => { const _handleOnPostPress = (permlink, author) => {
@ -82,117 +119,116 @@ const PostBody = ({
} }
}; };
const _hasParentTag = (node, name) => { const test = body.replace(/<a/g, '<a target="_blank"');
if (!node.parent) { const customStyle = `
return false; * {
} color: ${EStyleSheet.value('$primaryBlack')};
font-family: Roboto, sans-serif;
if (node.name === name) { max-width: 100%;
return true; }
} body {
color: ${EStyleSheet.value('$primaryBlack')};
return _hasParentTag(node.parent, name); display: flex;
}; align-items: center;
}
const _alterNode = (node, isComment) => { a {
if (isComment) { color: ${EStyleSheet.value('$primaryBlue')};
if (node.name === 'img') { cursor: pointer;
node.attribs.style = `max-width: ${WIDTH - 50}px; height: 100px; width: ${WIDTH - text-decoration: underline;
50}px; text-align: center;`; }
} img {
} else if (node.name === 'a') { align-self: 'center';
node.attribs.style = 'text-decoration: underline'; max-width: 100%;
} }
center {
if (node.name === 'img') { text-align: 'center';
node.attribs.style = 'text-align: center;'; align-items: 'center';
if (_hasParentTag(node, 'td')) { justify-content: 'center';
node.attribs.style = `max-width: ${WIDTH / 2 - 20}px; `; }
} th {
} flex: 1;
justify-content: 'center';
if (node.name === 'div' && node.attribs && node.attribs.class) { font-weight: 'bold';
const _className = node.attribs.class; color: ${EStyleSheet.value('$primaryBlack')};
font-size: 14;
if (_className === 'pull-right') { padding: 5;
node.attribs.style = 'text-align: right; align-self: flex-end;'; }
} tr {
background-color: ${EStyleSheet.value('$darkIconColor')};
if (_className === 'pull-left') { flex-direction: 'row';
node.attribs.style = 'text-align: left; align-self: flex-start;'; }
} td: {
border-width: 0.5;
if (_className === 'text-justify') { border-color: ${EStyleSheet.value('$tableBorderColor')};
node.attribs.style = 'text-align: justify; text-justify: inter-word; letter-spacing: 0px;'; flex: 1;
} padding: 10;
background-color: ${EStyleSheet.value('$tableTrColor')};
if (_className === 'phishy') { }
node.attribs.style = 'color: red'; blockquote: {
} border-left-width: 5;
} border-color: ${EStyleSheet.value('$darkIconColor')};
}; padding-left: 5;
}
const _alterData = node => { code: {
if ( background-color: ${EStyleSheet.value('$darkIconColor')};
node.type === 'text' && font-family: ${EStyleSheet.value('$editorFont')};
node.data.includes('markdown-author-link') && }
node.parent && center: {
getParentsTagsRecursively(node.parent).includes('code') text-align: 'center';
) { align-items: 'center';
return node.data.replace(/<[^>]*>/g, ''); justify-content: 'center';
} }
}; .markdown-video-link {
max-width: 100%;
const _initialDimensions = isComment }
? { width: WIDTH - 50, height: 80 } .pull-right {
: { width: WIDTH, height: 216 }; float: right;
}
const _customRenderer = { .pull-left {
a: (htmlAttribs, children, convertedCSSStyles, passProps) => { float: left;
if (passProps.parentWrapper === 'Text') { }
return ( .pull-left,
<Text .pull-right {
key={passProps.key} max-width: calc(50% - 10px);
{...htmlAttribs} padding-left: 10px;
onPress={() => _handleOnLinkPress(htmlAttribs['data-href'], htmlAttribs)} margin-bottom: 10px;
> box-sizing: border-box;
{children} }
</Text> .phishy {
); display: inline;
} color: red;
return ( }
<TouchableOpacity
key={passProps.key}
{...htmlAttribs}
onPress={() => _handleOnLinkPress(htmlAttribs['data-href'], htmlAttribs)}
>
{children}
</TouchableOpacity>
);
},
br: (htmlAttribs, children, passProps) => {
return <Text {...passProps}>{'\n'}</Text>;
},
};
.text-justify {
text-align: justify;
text-justify: inter-word;
letter-spacing: 0px;
}
`;
return ( return (
<HTML <Fragment>
html={body} <AutoHeightWebView
onLinkPress={(evt, href, hrefAtr) => _handleOnLinkPress(evt, href, hrefAtr)} source={{
containerStyle={isComment ? styles.commentContainer : styles.container} html: test,
textSelectable={textSelectable} }}
tagsStyles={isComment ? { img: { height: 120 } } : styles} style={{ width: isComment ? WIDTH - 61 : WIDTH - 32 }}
ignoredTags={['script']} customStyle={customStyle}
debug={false} onMessage={_handleOnLinkPress}
staticContentMaxWidth={WIDTH - 33} customScript={script.toString()}
imagesInitialDimensions={_initialDimensions} renderLoading={() => (isComment ? <CommentPlaceHolder /> : <PostPlaceHolder />)}
baseFontStyle={styles.text} startInLoadingState={true}
imagesMaxWidth={isComment ? WIDTH - 50 : WIDTH} onShouldStartLoadWithRequest={false}
alterNode={e => _alterNode(e, isComment)} scrollEnabled={false}
alterData={e => _alterData(e)} />
renderers={_customRenderer} </Fragment>
/>
); );
}; };
export default injectIntl(withNavigation(PostBody)); const areEqual = (prevProps, nextProps) => {
if (prevProps.body !== nextProps.body) {
return true;
}
return false;
};
export default React.memo(injectIntl(withNavigation(PostBody)), areEqual);

View File

@ -162,7 +162,9 @@ class PostDisplayView extends PureComponent {
const isGetComment = scrollHeight + 300 > postHeight; const isGetComment = scrollHeight + 300 > postHeight;
const formatedTime = post && getTimeFromNow(post.created); const formatedTime = post && getTimeFromNow(post.created);
if (isGetComment && !isLoadedComments) this.setState({ isLoadedComments: true }); if (isGetComment && !isLoadedComments) {
this.setState({ isLoadedComments: true });
}
if (isPostUnavailable) { if (isPostUnavailable) {
return ( return (

View File

@ -7458,6 +7458,13 @@ react-native-autocomplete-input@^4.1.0:
resolved "https://registry.yarnpkg.com/react-native-autocomplete-input/-/react-native-autocomplete-input-4.1.0.tgz#979ece28d891b245ecb967b6d31f1f924445b8ab" resolved "https://registry.yarnpkg.com/react-native-autocomplete-input/-/react-native-autocomplete-input-4.1.0.tgz#979ece28d891b245ecb967b6d31f1f924445b8ab"
integrity sha512-Yn4GulZ9F6tde74UUGZHdVFeYWVuL7+EbUZy6kt+QHrzMc5B4OuRop1FT4RyWLpvbySW/vvqYgj9LAmlzkuEqA== integrity sha512-Yn4GulZ9F6tde74UUGZHdVFeYWVuL7+EbUZy6kt+QHrzMc5B4OuRop1FT4RyWLpvbySW/vvqYgj9LAmlzkuEqA==
react-native-autoheight-webview@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/react-native-autoheight-webview/-/react-native-autoheight-webview-1.2.2.tgz#5b65fdad47f552cd154fc9dd916aebc69492cc24"
integrity sha512-Did1IBXxY3fmGyGn7ioT3Osu9Uk7H+4M/rc6bal83cwEjxsX0lUDhzXOMNJh4D6Wh5eRne2r7zPz52tKjeLv8g==
dependencies:
prop-types "^15.7.2"
react-native-code-push@esteemapp/react-native-code-push: react-native-code-push@esteemapp/react-native-code-push:
version "1000.0.0-beta" version "1000.0.0-beta"
resolved "https://codeload.github.com/esteemapp/react-native-code-push/tar.gz/c07b7023c1212dc5d9231a0526a869d2501cb221" resolved "https://codeload.github.com/esteemapp/react-native-code-push/tar.gz/c07b7023c1212dc5d9231a0526a869d2501cb221"