diff --git a/android/app/build.gradle b/android/app/build.gradle index af230d579..501cbad02 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -144,7 +144,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode versionMajor * 10000 + versionMinor * 100 + versionPatch - versionName "3.0.25" + versionName "3.0.26" resValue "string", "build_config_package", "app.esteem.mobile.android" multiDexEnabled true // react-native-image-crop-picker diff --git a/ios/Ecency-tvOS/Info.plist b/ios/Ecency-tvOS/Info.plist index 657cc0ea6..06f4cd14d 100644 --- a/ios/Ecency-tvOS/Info.plist +++ b/ios/Ecency-tvOS/Info.plist @@ -15,11 +15,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.0.25 + 3.0.26 CFBundleSignature ???? CFBundleVersion - 2804 + 2805 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/ios/Ecency-tvOSTests/Info.plist b/ios/Ecency-tvOSTests/Info.plist index 7eff350a0..ae8a18efd 100644 --- a/ios/Ecency-tvOSTests/Info.plist +++ b/ios/Ecency-tvOSTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 3.0.25 + 3.0.26 CFBundleSignature ???? CFBundleVersion - 2804 + 2805 diff --git a/ios/Ecency.xcodeproj/project.pbxproj b/ios/Ecency.xcodeproj/project.pbxproj index 06168e721..c5d32ac9b 100644 --- a/ios/Ecency.xcodeproj/project.pbxproj +++ b/ios/Ecency.xcodeproj/project.pbxproj @@ -1129,7 +1129,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2804; + CURRENT_PROJECT_VERSION = 2805; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = 75B6RXTKGT; EXCLUDED_ARCHS = ""; @@ -1208,7 +1208,7 @@ CODE_SIGN_ENTITLEMENTS = Ecency/Ecency.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2804; + CURRENT_PROJECT_VERSION = 2805; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = 75B6RXTKGT; EXCLUDED_ARCHS = ""; diff --git a/ios/Ecency/Info.plist b/ios/Ecency/Info.plist index 24165baf0..6296cf446 100644 --- a/ios/Ecency/Info.plist +++ b/ios/Ecency/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 3.0.25 + 3.0.26 CFBundleSignature ???? CFBundleURLTypes diff --git a/ios/EcencyTests/Info.plist b/ios/EcencyTests/Info.plist index f275b8316..6c797b853 100644 --- a/ios/EcencyTests/Info.plist +++ b/ios/EcencyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 3.0.25 + 3.0.26 CFBundleSignature ???? CFBundleVersion - 2804 + 2805 diff --git a/ios/eshare/Info.plist b/ios/eshare/Info.plist index 026af48ff..499823f0f 100644 --- a/ios/eshare/Info.plist +++ b/ios/eshare/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 3.0.25 + 3.0.26 CFBundleVersion - 2804 + 2805 NSExtension NSExtensionAttributes diff --git a/package.json b/package.json index 5d8de6504..03f814e0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ecency", - "version": "3.0.25", + "version": "3.0.26", "displayName": "Ecency", "private": true, "rnpm": { @@ -18,7 +18,7 @@ "test": "node node_modules/jest/bin/jest.js --watch", "lint": "eslint src/", "lint:fix": "eslint src/ --fix", - "format": "prettier --write 'src/**/*.{js,jsx}' && yarn lint:fix --fix", + "format": "prettier --write 'src/**/*.{js,jsx,ts,tsx}' && yarn lint:fix --fix", "lint-staged": "lint-staged", "clear": "watchman watch-del-all && rm -rf $TMPDIR/react-native-packager-cache-* && rm -rf $TMPDIR/metro-bundler-cache-* && rm -rf node_modules/ && yarn && yarn start -- --reset-cache", "bump-patch": "npm version patch --no-git-tag-version", @@ -35,6 +35,7 @@ "@esteemapp/react-native-multi-slider": "^1.1.0", "@esteemapp/react-native-slider": "^0.12.0", "@hiveio/dhive": "^1.0.1", + "@native-html/iframe-plugin": "^2.6.1", "@react-native-community/async-storage": "^1.11.0", "@react-native-community/cameraroll": "^1.3.0", "@react-native-community/cli-platform-ios": "^4.10.1", @@ -100,8 +101,8 @@ "react-native-linear-gradient": "^2.4.2", "react-native-matomo-sdk": "feruzm/react-native-matomo-sdk", "react-native-modal": "^11.5.6", - "react-native-modal-dropdown": "^1.0.1", "react-native-modal-popover": "^2.1.0", + "react-native-modal-dropdown": "^1.0.2", "react-native-modal-translucent": "^5.0.0", "react-native-navigation-bar-color": "^1.0.0", "react-native-os": "^1.0.1", diff --git a/src/components/basicHeader/view/basicHeaderView.js b/src/components/basicHeader/view/basicHeaderView.js index a618653d3..432488ecc 100644 --- a/src/components/basicHeader/view/basicHeaderView.js +++ b/src/components/basicHeader/view/basicHeaderView.js @@ -1,7 +1,6 @@ import React, { useState, Fragment, useRef } from 'react'; import { View, Text, ActivityIndicator, SafeAreaView } from 'react-native'; import { injectIntl } from 'react-intl'; -import ActionSheet from 'react-native-actionsheet'; import { useSelector } from 'react-redux'; import moment from 'moment'; @@ -15,6 +14,7 @@ import { DateTimePicker } from '../../dateTimePicker'; // Constants // Styles import styles from './basicHeaderStyles'; +import { OptionsModal } from '../../atoms'; const BasicHeaderView = ({ disabled, @@ -324,7 +324,7 @@ const BasicHeaderView = ({ /> - - actionSheet.current.show()} iconType="MaterialIcons" /> - - { + const _word = extractWordAtIndex(text, selection.start); + const _insertAt = text.indexOf(_word, selection.start - _word.length); + const _text = replaceBetween(text, {start:_insertAt, end:_insertAt + _word.length}, `@${username} `) + const _newPos = _insertAt + username.length + 2; + const _selection = { start: _newPos, end: _newPos }; + setTextAndSelection({ selection: _selection, text: _text }); +}; \ No newline at end of file diff --git a/src/components/markdownEditor/view/markdownEditorStyles.js b/src/components/markdownEditor/view/markdownEditorStyles.js index 6a12e7f7e..4adfa8c25 100644 --- a/src/components/markdownEditor/view/markdownEditorStyles.js +++ b/src/components/markdownEditor/view/markdownEditorStyles.js @@ -110,4 +110,31 @@ export default EStyleSheet.create({ bottom: 56, }, }), + searchAccountsContainer: Platform.select({ + //absolute positioning makes button hide behind keyboard on ios + ios: { + marginBottom: 12, + paddingTop: 8, + }, + //on android the appearing of button was causing momentary glitch with ios variant style + android: { + position: 'absolute', + bottom: 56, + }, + }), + userBubble: { + flexDirection: 'row', + alignItems: 'center', + marginHorizontal: 4, + paddingHorizontal: 4, + paddingVertical: 4, + backgroundColor: '$primaryBlue', + borderRadius: 24, + }, + userBubbleText: { + fontSize: 16, + color: '$white', + marginLeft: 6, + marginRight: 8, + }, }); diff --git a/src/components/markdownEditor/view/markdownEditorView.js b/src/components/markdownEditor/view/markdownEditorView.js index 9042e02a2..f9d5b9d44 100644 --- a/src/components/markdownEditor/view/markdownEditorView.js +++ b/src/components/markdownEditor/view/markdownEditorView.js @@ -8,7 +8,6 @@ import { ScrollView, TouchableOpacity, } from 'react-native'; -import ActionSheet from 'react-native-actionsheet'; import { renderPostBody } from '@ecency/render-helper'; import { useDispatch, useSelector } from 'react-redux'; import { View as AnimatedView } from 'react-native-animatable'; @@ -47,6 +46,9 @@ import styles from './markdownEditorStyles'; import applySnippet from './formats/applySnippet'; import { MainButton } from '../../mainButton'; import isAndroidOreo from '../../../utils/isAndroidOreo'; +import { OptionsModal } from '../../atoms'; +import { UsernameAutofillBar } from './usernameAutofillBar'; +import applyUsername from './formats/applyUsername'; const MIN_BODY_INPUT_HEIGHT = 300; @@ -186,6 +188,15 @@ const MarkdownEditorView = ({ dispatch(toggleAccountsBottomSheet(!isVisibleAccountsBottomSheet)); }; + const _onApplyUsername = (username) => { + applyUsername({ + text, + selection, + setTextAndSelection: _setTextAndSelection, + username, + }); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps const _changeText = useCallback((input) => { setText(input); @@ -459,8 +470,9 @@ const MarkdownEditorView = ({ behavior={Platform.OS === 'ios' ? 'padding' : 'height'} > {isAndroidOreo() ? _renderEditorWithoutScroll() : _renderEditorWithScroll()} - + {_renderFloatingDraftButton()} + {!isPreviewActive && _renderEditorButtons()} - - void; +} + +export const UsernameAutofillBar = ({text, selection, onApplyUsername}:Props) => { + + const [searchedUsers, setSearchedUsers] = useState([]) + const [query, setQuery] = useState(''); + + useEffect(() => { + if (selection.start === selection.end && text) { + const word = extractWordAtIndex(text, selection.start); + console.log('selection word is: ', word); + if (word.startsWith('@') && word.length > 3) { + _handleUserSearch(word.substring(1)); + } else { + setSearchedUsers([]); + setQuery('') + _handleUserSearch.cancel(); + } + } + }, [text, selection]) + + + + const _handleUserSearch = debounce(async (username) => { + if(query !== username){ + let users = []; + if (username) { + setQuery(username) + users = await lookupAccounts(username); + console.log('result users for', username, users); + } + setSearchedUsers(users); + } + + }, 200, {leading:true}); + + + + const _onUserSelect = (username) => { + onApplyUsername(username) + setSearchedUsers([]); + setQuery('') + }; + + if(!searchedUsers || searchedUsers.length === 0 || query === ''){ + return null; + } + + const _renderItem = ({item}:{item:string}) => { + + const username = item; + return ( + {_onUserSelect(username)}}> + + + {username} + + + ) + } + + return ( + + `searched-user-${item}`} + /> + + ) + } diff --git a/src/components/postBoost/postBoostView.js b/src/components/postBoost/postBoostView.js index 74717a98c..c2c5a4560 100644 --- a/src/components/postBoost/postBoostView.js +++ b/src/components/postBoost/postBoostView.js @@ -3,7 +3,6 @@ import { injectIntl } from 'react-intl'; import { Text, View, ScrollView, TouchableOpacity, Alert } from 'react-native'; import { WebView } from 'react-native-webview'; import get from 'lodash/get'; -import ActionSheet from 'react-native-actionsheet'; import Autocomplete from '@esteemapp/react-native-autocomplete-input'; import { Icon, TextInput } from '..'; import { hsOptions } from '../../constants/hsOptions'; @@ -21,6 +20,7 @@ import { Modal } from '../modal'; // Styles import styles from './postBoostStyles'; +import { OptionsModal } from '../atoms'; class BoostPostScreen extends PureComponent { /* Props @@ -240,7 +240,7 @@ class BoostPostScreen extends PureComponent { - - (this.ActionSheet = o)} options={['Reblog', intl.formatMessage({ id: 'alert.cancel' })]} title={intl.formatMessage({ id: 'post.reblog_alert' })} diff --git a/src/components/postElements/body/view/commentBodyView.tsx b/src/components/postElements/body/view/commentBodyView.tsx index bf7b79d29..730816e4d 100644 --- a/src/components/postElements/body/view/commentBodyView.tsx +++ b/src/components/postElements/body/view/commentBodyView.tsx @@ -13,7 +13,7 @@ import { navigate } from '../../../../navigation/service'; // Constants import { default as ROUTES } from '../../../../constants/routeNames'; -import { PostHtmlRenderer, TextButton } from '../../..'; +import { PostHtmlRenderer, TextButton, VideoPlayer } from '../../..'; // Styles import styles from './commentBodyStyles'; @@ -21,14 +21,12 @@ import styles from './commentBodyStyles'; // Services and Actions import { writeToClipboard } from '../../../../utils/clipboard'; import { toastNotification } from '../../../../redux/actions/uiAction'; -import VideoPlayerSheet from './videoPlayerSheet'; import { LongPressGestureHandler, State } from 'react-native-gesture-handler'; import { useCallback } from 'react'; import { OptionsModal } from '../../../atoms'; import { useAppDispatch } from '../../../../hooks'; import { isCommunity } from '../../../../utils/communityValidation'; import { GLOBAL_POST_FILTERS_VALUE } from '../../../../constants/options/filters'; -import { startsWith } from 'core-js/core/string'; const WIDTH = Dimensions.get('window').width; @@ -337,6 +335,7 @@ const CommentBody = ({ - + ); diff --git a/src/components/postElements/body/view/postBodyView.js b/src/components/postElements/body/view/postBodyView.js index 052931d6b..8979014ad 100644 --- a/src/components/postElements/body/view/postBodyView.js +++ b/src/components/postElements/body/view/postBodyView.js @@ -15,11 +15,10 @@ import { toastNotification } from '../../../../redux/actions/uiAction'; // Constants import { default as ROUTES } from '../../../../constants/routeNames'; -import VideoPlayerSheet from './videoPlayerSheet'; import { OptionsModal } from '../../../atoms'; import { isCommunity } from '../../../../utils/communityValidation'; import { GLOBAL_POST_FILTERS_VALUE } from '../../../../constants/options/filters'; -import { PostHtmlRenderer } from '../../..'; +import { PostHtmlRenderer, VideoPlayer } from '../../..'; const WIDTH = Dimensions.get('window').width; @@ -294,7 +293,8 @@ const PostBody = ({ navigation, body, dispatch, onLoadEnd }) => { setVideoUrl(null); }} > - { - - const PLAYER_HEIGHT = Dimensions.get('screen').width * (9/16); - - const [shouldPlay, setShouldPlay] = useState(false); - const [loading, setLoading] = useState(true); - - const _onReady = () => { - setLoading(false) - setShouldPlay(true); - } - - const _onChangeState = (event:string) => { - setShouldPlay(!(event == 'paused' || event == 'ended')); - } - - const _onError = () => { - setLoading(false) - } - - const initialParams:InitialPlayerParams = { - start:startTime - } - - return ( - - - {youtubeVideoId && - - - }{ - videoUrl && - - { - setLoading(false); - }} - onLoadStart={()=>{ - setLoading(true); - }} - source={{uri:videoUrl}} - /> - - - } - {loading && } - - ); -}; - -export default VideoPlayerSheet; - -const styles = StyleSheet.create({ - container: { - paddingVertical:16, - }, - activityIndicator: { - position:'absolute', alignItems:'center', justifyContent:'center', top:0, bottom:0, left:0, right:0} -}); diff --git a/src/components/postHtmlRenderer/postHtmlRenderer.tsx b/src/components/postHtmlRenderer/postHtmlRenderer.tsx index 1f0bd0e41..24eff092c 100644 --- a/src/components/postHtmlRenderer/postHtmlRenderer.tsx +++ b/src/components/postHtmlRenderer/postHtmlRenderer.tsx @@ -1,28 +1,33 @@ -import React, { memo } from "react"; -import RenderHTML, { CustomRendererProps, Element, TNode } from "react-native-render-html"; -import styles from "./postHtmlRendererStyles"; -import { LinkData, parseLinkData } from "./linkDataParser"; -import VideoThumb from "./videoThumb"; -import { AutoHeightImage } from "../autoHeightImage/autoHeightImage"; - +import React, { memo } from 'react'; +import RenderHTML, { CustomRendererProps, Element, TNode } from 'react-native-render-html'; +import styles from './postHtmlRendererStyles'; +import { LinkData, parseLinkData } from './linkDataParser'; +import VideoThumb from './videoThumb'; +import { AutoHeightImage } from '../autoHeightImage/autoHeightImage'; +import { useHtmlIframeProps, iframeModel } from '@native-html/iframe-plugin'; +import WebView from 'react-native-webview'; +import { VideoPlayer } from '..'; interface PostHtmlRendererProps { - contentWidth:number; - body:string; - onLoaded?:()=>void; - setSelectedImage:(imgUrl:string)=>void; - setSelectedLink:(url:string)=>void; - onElementIsImage:(imgUrl:string)=>void; - handleOnPostPress:(permlink:string, authro:string)=>void; - handleOnUserPress:(username:string)=>void; - handleTagPress:(tag:string, filter?:string)=>void; - handleVideoPress:(videoUrl:string)=>void; - handleYoutubePress:(videoId:string, startTime:number)=>void; + contentWidth: number; + body: string; + isComment?: boolean; + onLoaded?: () => void; + setSelectedImage: (imgUrl: string) => void; + setSelectedLink: (url: string) => void; + onElementIsImage: (imgUrl: string) => void; + handleOnPostPress: (permlink: string, authro: string) => void; + handleOnUserPress: (username: string) => void; + handleTagPress: (tag: string, filter?: string) => void; + handleVideoPress: (videoUrl: string) => void; + handleYoutubePress: (videoId: string, startTime: number) => void; } -export const PostHtmlRenderer = memo(({ +export const PostHtmlRenderer = memo( + ({ contentWidth, body, + isComment, onLoaded, setSelectedImage, setSelectedLink, @@ -32,185 +37,174 @@ export const PostHtmlRenderer = memo(({ handleTagPress, handleVideoPress, handleYoutubePress, - }:PostHtmlRendererProps) => { + }: PostHtmlRendererProps) => { + //new renderer functions + body = body.replace(/
/g, '
').replace(/<\/center>/g, '
'); - //new renderer functions - body = body.replace(/
/g, '
').replace(/<\/center>/g,'
'); + console.log('Comment body:', body); - - console.log("Comment body:", body); - - const _handleOnLinkPress = (data:LinkData) => { - - if(!data){ - return; - } - - const { - type, - href, - author, - permlink, - tag, - youtubeId, - startTime, - filter, - videoHref, - community - } = data; - - try { - - switch (type) { - case '_external': - case 'markdown-external-link': - setSelectedLink(href); - break; - case 'markdown-author-link': - if (handleOnUserPress) { - handleOnUserPress(author); - } - break; - case 'markdown-post-link': - if (handleOnPostPress) { - handleOnPostPress(permlink, author); - } - break; - case 'markdown-tag-link': - if(handleTagPress){ - handleTagPress(tag, filter); - } - break; - - case 'markdown-video-link': - if(handleVideoPress){ - handleVideoPress(videoHref) - } - break; - case 'markdown-video-link-youtube': - if(handleYoutubePress){ - handleYoutubePress(youtubeId, startTime) - } - - break; - - //unused cases - case 'markdown-witnesses-link': - setSelectedLink(href); - break; - - case 'markdown-proposal-link': - setSelectedLink(href); - break; - - case 'markdown-community-link': - //tag press also handles community by default - if(handleTagPress){ - handleTagPress(community, filter) - } - break; - - default: - break; + const _handleOnLinkPress = (data: LinkData) => { + if (!data) { + return; } - } catch (error) {} - }; - - - const _onElement = (element:Element) => { - if(element.tagName === 'img' && element.attribs.src){ + + const { + type, + href, + author, + permlink, + tag, + youtubeId, + startTime, + filter, + videoHref, + community, + } = data; + + try { + switch (type) { + case '_external': + case 'markdown-external-link': + setSelectedLink(href); + break; + case 'markdown-author-link': + if (handleOnUserPress) { + handleOnUserPress(author); + } + break; + case 'markdown-post-link': + if (handleOnPostPress) { + handleOnPostPress(permlink, author); + } + break; + case 'markdown-tag-link': + if (handleTagPress) { + handleTagPress(tag, filter); + } + break; + + case 'markdown-video-link': + if (handleVideoPress) { + handleVideoPress(videoHref); + } + break; + case 'markdown-video-link-youtube': + if (handleYoutubePress) { + handleYoutubePress(youtubeId, startTime); + } + + break; + + //unused cases + case 'markdown-witnesses-link': + setSelectedLink(href); + break; + + case 'markdown-proposal-link': + setSelectedLink(href); + break; + + case 'markdown-community-link': + //tag press also handles community by default + if (handleTagPress) { + handleTagPress(community, filter); + } + break; + + default: + break; + } + } catch (error) {} + }; + + const _onElement = (element: Element) => { + if (element.tagName === 'img' && element.attribs.src) { const imgUrl = element.attribs.src; - console.log("img element detected", imgUrl); - onElementIsImage(imgUrl) + console.log('img element detected', imgUrl); + onElementIsImage(imgUrl); } }; - - const _anchorRenderer = ({ - InternalRenderer, - tnode, - ...props - }:CustomRendererProps) => { - + const _anchorRenderer = ({ InternalRenderer, tnode, ...props }: CustomRendererProps) => { + const parsedTnode = parseLinkData(tnode); const _onPress = () => { - console.log("Link Pressed:", tnode) + console.log('Link Pressed:', tnode); const data = parseLinkData(tnode); _handleOnLinkPress(data); }; + + //process video link if(tnode.classes?.indexOf('markdown-video-link') >= 0){ - const imgElement = tnode.children.find((child)=>{ - return child.classes.indexOf('video-thumbnail') > 0 ? true:false - }) - if(!imgElement){ + if(isComment){ + const imgElement = tnode.children.find((child) => { + return child.classes.indexOf('video-thumbnail') > 0 ? true : false; + }); + if (!imgElement) { + return ; + } + } else { return ( - - ) + + ); } } - - - return ( - - ); - } + + + return ; + }; + //this method checks if image is a child of table column //and calculates img width accordingly, //returns full width if img is not part of table - const getMaxImageWidth = (tnode:TNode)=>{ - + const getMaxImageWidth = (tnode: TNode) => { //return full width if not parent exist - if(!tnode.parent || tnode.parent.tagName === 'body'){ + if (!tnode.parent || tnode.parent.tagName === 'body') { return contentWidth; } //return divided width based on number td tags - if(tnode.parent.tagName === 'td'){ - const cols = tnode.parent.parent.children.length - return contentWidth/cols; + if (tnode.parent.tagName === 'td') { + const cols = tnode.parent.parent.children.length; + return contentWidth / cols; } //check next parent return getMaxImageWidth(tnode.parent); - } - - - const _imageRenderer = ({ - tnode, - }:CustomRendererProps) => { - + }; + + const _imageRenderer = ({ tnode }: CustomRendererProps) => { const imgUrl = tnode.attributes.src; const _onPress = () => { - console.log("Image Pressed:", imgUrl) + console.log('Image Pressed:', imgUrl); setSelectedImage(imgUrl); }; - + const isVideoThumb = tnode.classes?.indexOf('video-thumbnail') >= 0; const isAnchored = tnode.parent?.tagName === 'a'; - - if(isVideoThumb){ - return ; - } - else { + if (isVideoThumb) { + return ; + } else { const maxImgWidth = getMaxImageWidth(tnode); return ( - - ) + ); } - - } - + }; /** * the para renderer is designd to remove margins from para @@ -218,58 +212,90 @@ export const PostHtmlRenderer = memo(({ * a weired misalignment of bullet and content * @returns Default Renderer */ - const _paraRenderer = ({ - TDefaultRenderer, - ...props - }:CustomRendererProps) => { + const _paraRenderer = ({ TDefaultRenderer, ...props }: CustomRendererProps) => { + props.style = props.tnode.parent.tagName === 'li' ? styles.pLi : styles.p; - props.style = props.tnode.parent.tagName === 'li' - ? styles.pLi - : styles.p + return ; + }; - return ( - - ) - } - - - return ( - - ) - }, (next, prev)=>next.body === prev.body) + + // iframe renderer for rendering iframes in body + const _iframeRenderer = function IframeRenderer(props) { + const iframeProps = useHtmlIframeProps(props); + const checkSrcRegex = /(.*?)\.(mp4|webm|ogg)$/gi; + const isVideoFormat = iframeProps.source.uri.match(checkSrcRegex); + + //this hack help avoid autoplaying fullscreened iframe videos; + const src = isVideoFormat ? + { + html: ` + `, + }:{ + uri: iframeProps.source.uri, + }; + + return ( + + ); + }; + + return ( + + ); + }, + (next, prev) => next.body === prev.body, +); diff --git a/src/components/postView/view/postDisplayView.js b/src/components/postView/view/postDisplayView.js index e75028763..d2643216f 100644 --- a/src/components/postView/view/postDisplayView.js +++ b/src/components/postView/view/postDisplayView.js @@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useRef, useState, Fragment } from 'react import { View, Text, ScrollView, Dimensions, SafeAreaView, RefreshControl } from 'react-native'; import { injectIntl } from 'react-intl'; import get from 'lodash/get'; -import ActionSheet from 'react-native-actionsheet'; // Providers import { userActivity } from '../../../providers/ecency/ePoint'; @@ -20,6 +19,7 @@ import { ParentPost } from '../../parentPost'; // Styles import styles from './postDisplayStyles'; +import { OptionsModal } from '../../atoms'; const HEIGHT = Dimensions.get('window').width; @@ -251,7 +251,7 @@ const PostDisplayView = ({ )} {post && _getTabBar(true)} - - item.id} renderItem={_renderItem} /> {`v${appVersion}, ${buildVersion}${storageT}`} - { + + const PLAYER_HEIGHT = contentWidth * (9 / 16); + + const [shouldPlay, setShouldPlay] = useState(false); + const [loading, setLoading] = useState(true); + + const _onReady = () => { + setLoading(false); + setShouldPlay(disableAutoplay ? false : true); + console.log('ready'); + }; + + const _onChangeState = (event: string) => { + console.log(event); + setShouldPlay(!(event == 'paused' || event == 'ended')); + }; + + const _onError = () => { + console.log('error!'); + setLoading(false); + }; + + const initialParams: InitialPlayerParams = { + start: startTime, + }; + + return ( + + {mode === 'youtube' && youtubeVideoId && ( + + + + )} + {((mode === 'source' && source) || (mode === 'url' && videoUrl)) && ( + + { + setLoading(false); + }} + onLoadStart={() => { + setLoading(true); + }} + source={source || { uri: videoUrl }} + style={{ width: contentWidth, height: PLAYER_HEIGHT}} + startInLoadingState={true} + onShouldStartLoadWithRequest={() => true} + mediaPlaybackRequiresUserAction={true} + allowsInlineMediaPlayback={true} + /> + + )} + {loading && } + + ); +}; + +export default VideoPlayer; + +const styles = StyleSheet.create({ + container: { + paddingVertical: 16, + }, + activityIndicator: { + position: 'absolute', + alignItems: 'center', + justifyContent: 'center', + top: 0, + bottom: 0, + left: 0, + right: 0, + }, +}); diff --git a/src/screens/profileEdit/screen/profileEditScreen.js b/src/screens/profileEdit/screen/profileEditScreen.js index fa71e0702..5b5df2897 100644 --- a/src/screens/profileEdit/screen/profileEditScreen.js +++ b/src/screens/profileEdit/screen/profileEditScreen.js @@ -2,11 +2,11 @@ import React, { PureComponent, Fragment } from 'react'; import { StatusBar } from 'react-native'; import { injectIntl } from 'react-intl'; import get from 'lodash/get'; -import ActionSheet from 'react-native-actionsheet'; import { ProfileEditContainer } from '../../../containers'; import { AvatarHeader, ProfileEditForm } from '../../../components'; +import { OptionsModal } from '../../../components/atoms'; class ProfileEditScreen extends PureComponent { /* Props @@ -81,7 +81,7 @@ class ProfileEditScreen extends PureComponent { handleOnSubmit={handleOnSubmit} /> - - - (index === 0 ? this._handleTransferAction() : null)} /> - - - (this.ActionSheet = o)} options={[ intl.formatMessage({ id: 'alert.confirm' }), diff --git a/src/utils/editor.ts b/src/utils/editor.ts index c2c8df62e..ffabb7aa0 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -44,6 +44,28 @@ export const generatePermlink = (title, random = false) => { return perm; }; +export const extractWordAtIndex = (text:string, index:number) => { + const END_REGEX = /[\s,]/ + let word = ''; + for(let i = index; i >= 0 && (!END_REGEX.test(text[i]) || i === index); i--){ + if(text[i]){ + word += text[i]; + } + } + word = word.split('').reverse().join(''); + + if(!END_REGEX.test(text[index])){ + for(let i = index + 1; i < text.length && !END_REGEX.test(text[i]); i++){ + if(text[i]){ + word += text[i]; + } + } + } + + return word; + +} + export const generateReplyPermlink = (toAuthor) => { if (!toAuthor) { return ''; diff --git a/yarn.lock b/yarn.lock index 1ea70a4fb..c1a4ef4b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1118,6 +1118,11 @@ resolved "https://registry.yarnpkg.com/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz#2dc8c57044de0340eb53a7ba602e59abf80dc799" integrity sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ== +"@formidable-webview/webshell@^2.6.0": + version "2.6.0" + resolved "https://registry.yarnpkg.com/@formidable-webview/webshell/-/webshell-2.6.0.tgz#64704c0b513206e71b23118b3c9d096f0d545005" + integrity sha512-FwQQDajg1xs7W3CUiUNJMvdjgLjKLDGzs0XPzoVg0Dunhold1Jg7w5pihUdvVugFlNtkSpXMA+du9QDHE8lmpg== + "@hapi/address@2.x.x": version "2.1.4" resolved "https://registry.yarnpkg.com/@hapi/address/-/address-2.1.4.tgz#5d67ed43f3fd41a69d4b9ff7b56e7c0d1d0a81e5" @@ -1412,6 +1417,21 @@ css-to-react-native "^3.0.0" csstype "^3.0.8" +"@native-html/iframe-plugin@^2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@native-html/iframe-plugin/-/iframe-plugin-2.6.1.tgz#5b9c36d9d500f82f0bcf654bc005922df4211158" + integrity sha512-PM2vFNT44n/UkCm9+OUn+cNSKgiMjaw7c7/2JnnztHDLVMtPIf52K/86miWNpQpxFoy1ouoLVOvfjFRhoPXjag== + dependencies: + "@formidable-webview/webshell" "^2.6.0" + "@native-html/plugins-core" "1.3.0" + "@types/prop-types" "^15.7.4" + prop-types "^15.7.2" + +"@native-html/plugins-core@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@native-html/plugins-core/-/plugins-core-1.3.0.tgz#f1f24622097551930d9dab0214c4929d00f7446e" + integrity sha512-vce35gqGJKa2oPDZVa2sKjucFFVK+3g8quLayeXiJtj5LzuS8TWGrBFTS5O4ToUtE02AJkCDOLwAguCzK2HWdQ== + "@native-html/transient-render-engine@^9.2.2": version "9.2.2" resolved "https://registry.yarnpkg.com/@native-html/transient-render-engine/-/transient-render-engine-9.2.2.tgz#00691518926ea47709185c3a25a786472c99a1f0" @@ -1784,6 +1804,11 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== +"@types/prop-types@^15.7.4": + version "15.7.4" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" + integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== + "@types/ramda@^0.27.40": version "0.27.44" resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.27.44.tgz#ba2283d67fcff366f7e68bd5124a0466e467967f" @@ -8599,10 +8624,10 @@ react-native-matomo-sdk@feruzm/react-native-matomo-sdk: version "0.4.1" resolved "https://codeload.github.com/feruzm/react-native-matomo-sdk/tar.gz/392b1cfca771b28005821ef909ffb9a2082156d9" -react-native-modal-dropdown@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/react-native-modal-dropdown/-/react-native-modal-dropdown-1.0.1.tgz#24a9396dcb5dadf92feea1deae1089bcef054b20" - integrity sha512-7QAuKvdsIMvz5N2FnRGrZKb+JR3bYFFelONdmfwp1UW3acOyrhF32H06aMD09x2PdBz3VaR5+3iApt9FYClktQ== +react-native-modal-dropdown@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/react-native-modal-dropdown/-/react-native-modal-dropdown-1.0.2.tgz#3e1efae5a5eacc42f44ac96468ea2f1b5bb0d759" + integrity sha512-X13RbwoQdaOb9Ffi7fjRVwh9OXauXIJhum0uXGzYZLMyoCQ5yt5cxf27P/YZbdh80Fb7bdPx6vTA4PTpUK2WDQ== dependencies: prop-types "^15.6.0"