From c202c1fc4da203d09b61114b25a23dbdcd12a69d Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Mon, 6 Jun 2022 19:36:05 +0500 Subject: [PATCH 01/16] reduced frequency of form update callback to update state --- .../markdownEditor/view/markdownEditorView.js | 9 --------- src/components/postForm/view/postFormView.js | 13 ++++++++++--- src/screens/editor/screen/editorScreen.tsx | 4 +--- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/components/markdownEditor/view/markdownEditorView.js b/src/components/markdownEditor/view/markdownEditorView.js index ed316f28b..8c3ab5c39 100644 --- a/src/components/markdownEditor/view/markdownEditorView.js +++ b/src/components/markdownEditor/view/markdownEditorView.js @@ -222,14 +222,6 @@ const MarkdownEditorView = ({ if (onChange) { onChange(input); } - - if (handleIsValid) { - handleIsValid(componentID, !!(input && input.length)); - } - - if (handleOnTextChange) { - handleOnTextChange(input); - } }); const _handleOnSelectionChange = async (event) => { @@ -267,7 +259,6 @@ const MarkdownEditorView = ({ _changeText(_text); }); - console.log('text : ', text); const _renderPreview = () => ( {text ? ( diff --git a/src/components/postForm/view/postFormView.js b/src/components/postForm/view/postFormView.js index 2f1246c13..aed241ee4 100644 --- a/src/components/postForm/view/postFormView.js +++ b/src/components/postForm/view/postFormView.js @@ -1,4 +1,5 @@ import React, { PureComponent, Fragment } from 'react'; +import { debounce } from 'lodash'; class PostFormView extends PureComponent { constructor(props) { @@ -18,9 +19,12 @@ class PostFormView extends PureComponent { }; _handleOnChange = (componentID, value, isValid = null) => { - const { handleFormUpdate } = this.props; - + const { handleFormUpdate, handleBodyChange } = this.props; + console.log('update fields state :', componentID, value); handleFormUpdate(componentID, value, !!isValid || !!value); + if (componentID === 'body') { + handleBodyChange(value); + } }; render() { @@ -33,7 +37,10 @@ class PostFormView extends PureComponent { return React.cloneElement(child, { onSubmitEditing: (item) => this._handleOnSubmitEditing(child.props.returnKeyType, item), - onChange: (value) => this._handleOnChange(child.props.componentID, value), + onChange: debounce( + (value) => this._handleOnChange(child.props.componentID, value), + 500, + ), returnKeyType: isFormValid ? 'done' : 'next', isPreviewActive, }); diff --git a/src/screens/editor/screen/editorScreen.tsx b/src/screens/editor/screen/editorScreen.tsx index 8e9e89759..4ad451e99 100644 --- a/src/screens/editor/screen/editorScreen.tsx +++ b/src/screens/editor/screen/editorScreen.tsx @@ -435,6 +435,7 @@ console.log('quickReplyText : ', quickReplyText); /> Date: Mon, 6 Jun 2022 20:32:30 +0500 Subject: [PATCH 02/16] reduced frequency username check --- .../markdownEditor/view/markdownEditorView.js | 3 -- .../view/usernameAutofillBar.tsx | 28 +++++++++++-------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/components/markdownEditor/view/markdownEditorView.js b/src/components/markdownEditor/view/markdownEditorView.js index 8c3ab5c39..1d0ad877d 100644 --- a/src/components/markdownEditor/view/markdownEditorView.js +++ b/src/components/markdownEditor/view/markdownEditorView.js @@ -64,9 +64,6 @@ const MarkdownEditorView = ({ isUploading, initialFields, onChange, - handleOnTextChange, - handleIsValid, - componentID, uploadedImage, isEdit, post, diff --git a/src/components/markdownEditor/view/usernameAutofillBar.tsx b/src/components/markdownEditor/view/usernameAutofillBar.tsx index 21b742142..ef18e8c37 100644 --- a/src/components/markdownEditor/view/usernameAutofillBar.tsx +++ b/src/components/markdownEditor/view/usernameAutofillBar.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect} from 'react'; +import React, {useState, useEffect, useCallback} from 'react'; import { View, FlatList, Text, TouchableOpacity } from "react-native" import { UserAvatar } from '../..'; import { lookupAccounts } from '../../../providers/hive/dhive'; @@ -22,21 +22,25 @@ export const UsernameAutofillBar = ({text, selection, onApplyUsername}:Props) => 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(); - } + _processTextForSearch(text, selection.start); } }, [text, selection]) + const _processTextForSearch = useCallback(debounce((text:string, index:number) => { + const word = extractWordAtIndex(text, index); + console.log('selection word is: ', word); + if (word.startsWith('@') && word.length > 1) { + _handleUserSearch(word.substring(1)); + } else { + setSearchedUsers([]); + setQuery('') + _handleUserSearch.cancel(); + } + }, 300, {leading:true}),[]); - const _handleUserSearch = debounce(async (username) => { + + const _handleUserSearch = useCallback(debounce(async (username) => { if(query !== username){ let users = []; if (username) { @@ -47,7 +51,7 @@ export const UsernameAutofillBar = ({text, selection, onApplyUsername}:Props) => setSearchedUsers(users); } - }, 200, {leading:true}); + }, 200, {leading:true}), []); From 4a28525f42848ed3c1273ca70090d6b2dc4888b1 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Mon, 6 Jun 2022 20:33:20 +0500 Subject: [PATCH 03/16] processing a selection of string for word at index, instead of whole body --- src/utils/editor.ts | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/utils/editor.ts b/src/utils/editor.ts index 907f5b9da..eeff47924 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -45,25 +45,40 @@ export const generatePermlink = (title, random = false) => { }; export const extractWordAtIndex = (text:string, index:number) => { + + const RANGE = 50; + + const _start = index - RANGE; + const _end = index + RANGE; + + const _length = text.length; + + const textChunk = text.substring(_start > 0 ? _start : 0, _end < _length ? _end : _length); + const indexChunk = index < 50 ? index : ( + _length - index < 50 ? textChunk.length - (_length - index) : + RANGE + ); + + console.log('char at index: ', textChunk[indexChunk]); + 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]; + for(let i = indexChunk; i >= 0 && (!END_REGEX.test(textChunk[i]) || i === indexChunk); i--){ + if(textChunk[i]){ + word += textChunk[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]; + if(!END_REGEX.test(textChunk[indexChunk])){ + for(let i = indexChunk + 1; i < textChunk.length && !END_REGEX.test(textChunk[i]); i++){ + if(textChunk[i]){ + word += textChunk[i]; } } } return word; - } export const generateReplyPermlink = (toAuthor) => { From c69799d2d44f5181f3ecf53bb5f18be36bd2446d Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Tue, 7 Jun 2022 13:26:03 +0500 Subject: [PATCH 04/16] loading uploads modal content only when modal is visible --- .../uploadsGalleryModal.tsx | 58 +++++++++++-------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/src/components/uploadsGalleryModal/uploadsGalleryModal.tsx b/src/components/uploadsGalleryModal/uploadsGalleryModal.tsx index 976d543da..43ea05663 100644 --- a/src/components/uploadsGalleryModal/uploadsGalleryModal.tsx +++ b/src/components/uploadsGalleryModal/uploadsGalleryModal.tsx @@ -69,6 +69,7 @@ export const UploadsGalleryModal = forwardRef(({ }, [uploadedImage]) + //save image to user gallery const _addUploadedImageToGallery = async () => { try { @@ -138,6 +139,8 @@ export const UploadsGalleryModal = forwardRef(({ setShowModal(false); } + + //renders footer with add snipept button and shows new snippet modal const _renderFloatingPanel = () => { @@ -242,7 +245,7 @@ export const UploadsGalleryModal = forwardRef(({ }; - const _renderHeaderContent = ( + const _renderHeaderContent = () => ( <> {isUploading && } @@ -251,30 +254,35 @@ export const UploadsGalleryModal = forwardRef(({ - const _renderContent = ( - - - - {_renderHeaderContent} - `item_${item.url}`} - renderItem={_renderItem} - ListEmptyComponent={_renderEmptyContent} - ListFooterComponent={} - extraData={indices} - numColumns={2} - refreshControl={ - - } - /> + const _renderContent = () => { + console.log("Rendering uploaded images") + return ( + + + + {_renderHeaderContent()} + `item_${item.url}`} + renderItem={_renderItem} + ListEmptyComponent={_renderEmptyContent} + ListFooterComponent={} + extraData={indices} + numColumns={2} + refreshControl={ + + } + /> + + {_renderFloatingPanel()} - {_renderFloatingPanel()} - - ) + ) + } + + return ( @@ -290,7 +298,7 @@ export const UploadsGalleryModal = forwardRef(({ animationType="slide" style={styles.modalStyle} > - {_renderContent} + {showModal && _renderContent()} ); From 38968d79a0f4082a5317dd07a2adc2c64886fba8 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Wed, 8 Jun 2022 12:53:30 +0500 Subject: [PATCH 05/16] removed unused modals and methods from editor screen and basic header --- ...basicHeaderView.js => basicHeaderView.tsx} | 119 +----------------- .../uploadsGalleryModal.tsx | 2 - src/screens/editor/screen/editorScreen.tsx | 19 +-- 3 files changed, 10 insertions(+), 130 deletions(-) rename src/components/basicHeader/view/{basicHeaderView.js => basicHeaderView.tsx} (66%) diff --git a/src/components/basicHeader/view/basicHeaderView.js b/src/components/basicHeader/view/basicHeaderView.tsx similarity index 66% rename from src/components/basicHeader/view/basicHeaderView.js rename to src/components/basicHeader/view/basicHeaderView.tsx index e603b01bf..c11fcab65 100644 --- a/src/components/basicHeader/view/basicHeaderView.js +++ b/src/components/basicHeader/view/basicHeaderView.tsx @@ -1,15 +1,12 @@ import React, { useState, Fragment, useRef } from 'react'; import { View, Text, ActivityIndicator, SafeAreaView } from 'react-native'; import { injectIntl } from 'react-intl'; -import { useSelector } from 'react-redux'; -import moment from 'moment'; // Components -import { TextButton, Modal, BeneficiaryModal } from '../..'; +import { TextButton } from '../..'; import { IconButton } from '../../iconButton'; import { DropdownButton } from '../../dropdownButton'; import { TextInput } from '../../textInput'; -import { DateTimePicker } from '../../dateTimePicker'; // Constants // Styles @@ -28,7 +25,6 @@ const BasicHeaderView = ({ intl, isDraftSaved, isDraftSaving, - draftId, isFormValid, isHasDropdown, isHasIcons, @@ -46,23 +42,14 @@ const BasicHeaderView = ({ title, handleOnSubmit, handleOnSearch, - handleDatePickerChange, handleRewardChange, - handleBeneficiaries, enableViewModeToggle, handleSettingsPress, - showThumbSelectionModal, }) => { + const [isInputVisible, setIsInputVisible] = useState(false); - const [beneficiaryModal, setBeneficiaryModal] = useState(false); - const [showScheduleModal, setShowScheduleModal] = useState(false); - const [scheduledDate, setScheduledDate] = useState(''); - - const username = useSelector((state) => state.account.currentAccount.name); - - const settingMenuRef = useRef(null); const rewardMenuRef = useRef(null); - const scheduleRef = useRef(null); + /** * @@ -90,27 +77,6 @@ const BasicHeaderView = ({ handleOnSearch(value); }; - const _handleSettingMenuSelect = (index) => { - switch (index) { - case 0: - setShowScheduleModal(true); - break; - case 1: - if (showThumbSelectionModal) { - showThumbSelectionModal(); - } - break; - case 2: - rewardMenuRef.current.show(); - break; - case 3: - setBeneficiaryModal(true); - break; - - default: - break; - } - }; const _handleRewardMenuSelect = (index) => { let rewardType = 'default'; @@ -131,32 +97,7 @@ const BasicHeaderView = ({ } }; - const _handleOnSaveBeneficiaries = (beneficiaries) => { - const _beneficiaries = beneficiaries.map((item) => ({ - account: item.account, - weight: item.weight, - })); - setBeneficiaryModal(false); - if (handleBeneficiaries) { - handleBeneficiaries(_beneficiaries); - } - }; - const _handleDatePickerChange = (datePickerValue) => { - setScheduledDate(datePickerValue); - }; - - const _onPressDone = () => { - let dateString = scheduledDate; - - if (dateString === '') { - dateString = moment().format(); - } - - setScheduledDate(''); - handleDatePickerChange(dateString); - setShowScheduleModal(false); - }; /** * @@ -291,57 +232,8 @@ const BasicHeaderView = ({ )} - setBeneficiaryModal(false)} - title={intl.formatMessage({ id: 'editor.beneficiaries' })} - animationType="slide" - style={styles.beneficiaryModal} - > - - - setShowScheduleModal(false)} - > - - - - - + + + ); }; diff --git a/src/components/uploadsGalleryModal/uploadsGalleryModal.tsx b/src/components/uploadsGalleryModal/uploadsGalleryModal.tsx index 43ea05663..507fb2153 100644 --- a/src/components/uploadsGalleryModal/uploadsGalleryModal.tsx +++ b/src/components/uploadsGalleryModal/uploadsGalleryModal.tsx @@ -283,8 +283,6 @@ export const UploadsGalleryModal = forwardRef(({ } - - return ( { - const { fields } = this.state; - if (this.thumbSelectionModalRef) { - this.thumbSelectionModalRef.show(fields.body); - } - }; _handleScheduleChange = (datetime:string|null) => { this.setState({ @@ -406,8 +399,8 @@ class EditorScreen extends Component { ); }; -console.log('fields :', fields); -console.log('quickReplyText : ', quickReplyText); + + return ( @@ -430,7 +423,6 @@ console.log('quickReplyText : ', quickReplyText); isReply={isReply} quickTitle={wordsCount > 0 && `${wordsCount} words`} rightButtonText={rightButtonText} - showThumbSelectionModal={this._showThumbSelectionModal} handleSettingsPress={this._handleSettingsPress} /> + {_renderCommunityModal()} - (this.thumbSelectionModalRef = componentRef)} - thumbIndex={thumbIndex} - onThumbSelection={this._handleOnThumbSelection} - /> + (this.postOptionsModalRef = componentRef)} body={fields.body} From 63ca2ef32451bb64fdf18d3d59e954c829f3e566 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Wed, 8 Jun 2022 19:12:45 +0500 Subject: [PATCH 06/16] using reference based text and selection tracker instead of state --- ...wnEditorView.js => markdownEditorView.tsx} | 98 ++++++++++--------- 1 file changed, 54 insertions(+), 44 deletions(-) rename src/components/markdownEditor/view/{markdownEditorView.js => markdownEditorView.tsx} (90%) diff --git a/src/components/markdownEditor/view/markdownEditorView.js b/src/components/markdownEditor/view/markdownEditorView.tsx similarity index 90% rename from src/components/markdownEditor/view/markdownEditorView.js rename to src/components/markdownEditor/view/markdownEditorView.tsx index 1d0ad877d..3209cb961 100644 --- a/src/components/markdownEditor/view/markdownEditorView.js +++ b/src/components/markdownEditor/view/markdownEditorView.tsx @@ -7,6 +7,7 @@ import { Platform, ScrollView, TouchableOpacity, + Alert, } from 'react-native'; import { renderPostBody, postBodySummary } from '@ecency/render-helper'; import { useDispatch, useSelector } from 'react-redux'; @@ -53,9 +54,9 @@ import { walkthrough } from '../../../redux/constants/walkthroughConstants'; const MIN_BODY_INPUT_HEIGHT = 300; + const MarkdownEditorView = ({ draftBody, - handleIsFormValid, handleOpenImagePicker, intl, isPreviewActive, @@ -79,8 +80,9 @@ const MarkdownEditorView = ({ }) => { const dispatch = useDispatch(); - const [text, setText] = useState(draftBody || ''); - const [selection, setSelection] = useState({ start: 0, end: 0 }); + const bodyText = useRef(''); + const bodySelection = useRef({ start: 0, end: 0 }); + const [editable, setEditable] = useState(true); const [bodyInputHeight, setBodyInputHeight] = useState(MIN_BODY_INPUT_HEIGHT); const [isSnippetsOpen, setIsSnippetsOpen] = useState(false); @@ -102,7 +104,7 @@ const MarkdownEditorView = ({ useEffect(() => { if (!isPreviewActive) { - _setTextAndSelection({ selection: { start: 0, end: 0 }, text }); + _setTextAndSelection({ selection: { start: 0, end: 0 }, text: bodyText.current }); } }, [isPreviewActive]); @@ -118,7 +120,7 @@ const MarkdownEditorView = ({ }, [onLoadDraftPress]); useEffect(() => { - if (text === '' && draftBody !== '') { + if (bodyText.current === '' && draftBody !== '') { let draftBodyLength = draftBody.length; _setTextAndSelection({ selection: { start: draftBodyLength, end: draftBodyLength }, @@ -162,8 +164,8 @@ const MarkdownEditorView = ({ useEffect(() => { if (uploadedImage && uploadedImage.shouldInsert && !isUploading) { applyMediaLink({ - text, - selection, + text: bodyText.current, + selection: bodySelection.current, setTextAndSelection: _setTextAndSelection, items: [{ url: uploadedImage.url, text: uploadedImage.hash }], }); @@ -175,7 +177,7 @@ const MarkdownEditorView = ({ }, [uploadedImage, isUploading]); useEffect(() => { - setText(draftBody); + bodyText.current = draftBody; }, [draftBody]); useEffect(() => { @@ -188,16 +190,13 @@ const MarkdownEditorView = ({ }, [autoFocusText]); useEffect(() => { - const nextText = text.replace(text, ''); + const nextText = bodyText.current.replace(bodyText.current, ''); if (nextText && nextText.length > 0) { - _changeText(text); + _changeText(bodyText.current); - if (handleIsFormValid) { - handleIsFormValid(text); - } } - }, [text]); + }, [bodyText.current]); const changeUser = async () => { dispatch(toggleAccountsBottomSheet(!isVisibleAccountsBottomSheet)); @@ -205,8 +204,8 @@ const MarkdownEditorView = ({ const _onApplyUsername = (username) => { applyUsername({ - text, - selection, + text: bodyText.current, + selection: bodySelection.current, setTextAndSelection: _setTextAndSelection, username, }); @@ -214,15 +213,17 @@ const MarkdownEditorView = ({ // eslint-disable-next-line react-hooks/exhaustive-deps const _changeText = useCallback((input) => { - setText(input); + bodyText.current = input; + //NOTE: onChange method is called by direct parent of MarkdownEditor that is PostForm, do not remove if (onChange) { onChange(input); } - }); + }, []); + const _handleOnSelectionChange = async (event) => { - setSelection(event.nativeEvent.selection); + bodySelection.current = event.nativeEvent.selection; }; const _handleOnContentSizeChange = async (event) => { @@ -237,29 +238,33 @@ const MarkdownEditorView = ({ text: _text, }); - // Workaround for iOS selection update issue - const isIos = Platform.OS === 'ios'; - if (isIos) { - setTimeout(() => { - inputRef.current.setNativeProps({ - selection: _selection, - }); - setSelection(_selection); - }, 100); - } else { + const _updateSelection = () => { + bodySelection.current = _selection inputRef.current.setNativeProps({ selection: _selection, }); - setSelection(_selection); } - setIsSnippetsOpen(false); + + // Workaround for iOS selection update issue + if (Platform.OS === 'ios') { + setTimeout(() => { + _updateSelection(); + }, 100); + } else { + _updateSelection() + } + + if (isSnippetsOpen) { + setIsSnippetsOpen(false); + } + _changeText(_text); - }); + }, []); const _renderPreview = () => ( - {text ? ( - + {bodyText.current ? ( + ) : ( ... )} @@ -268,8 +273,8 @@ const MarkdownEditorView = ({ const _handleOnSnippetReceived = (snippetText) => { applySnippet({ - text, - selection, + text: bodyText.current, + selection: bodySelection.current, setTextAndSelection: _setTextAndSelection, snippetText: `\n${snippetText}\n`, }); @@ -283,8 +288,8 @@ const MarkdownEditorView = ({ if (items.length) { applyMediaLink({ - text, - selection, + text: bodyText.current, + selection: bodySelection.current, setTextAndSelection: _setTextAndSelection, items, }); @@ -293,8 +298,8 @@ const MarkdownEditorView = ({ const _handleOnAddLinkPress = () => { insertLinkModalRef.current?.showModal({ - selectedText: text.slice(selection.start, selection.end), - selection: selection, + selectedText: bodyText.current.slice(bodySelection.current.start, bodySelection.current.end), + selection: bodySelection.current, }); inputRef.current?.blur(); }; @@ -303,7 +308,7 @@ const MarkdownEditorView = ({ }; const _handleInsertLink = ({ snippetText, selection }) => { applySnippet({ - text, + text: bodyText.current, selection, setTextAndSelection: _setTextAndSelection, snippetText, @@ -320,7 +325,12 @@ const MarkdownEditorView = ({ iconType={item.iconType} name={item.icon} onPress={() => - item.onPress({ text, selection, setTextAndSelection: _setTextAndSelection, item }) + item.onPress({ + text: bodyText.current, + selection: bodySelection.current, + setTextAndSelection: _setTextAndSelection, + item + }) } /> @@ -415,7 +425,7 @@ const MarkdownEditorView = ({ const _handleClear = (index) => { if (index === 0) { initialFields(); - setText(''); + _setTextAndSelection({ text: '', selection: { start: 0, end: 0 } }); } }; @@ -501,7 +511,7 @@ const MarkdownEditorView = ({ const _innerContent = ( <> {isAndroidOreo() ? _renderEditorWithoutScroll() : _renderEditorWithScroll()} - + {_renderFloatingDraftButton()} {!isPreviewActive && _renderEditorButtons()} From ad94a2da062eceeb5adde1aae9acb31238b1eac9 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Wed, 8 Jun 2022 19:21:13 +0500 Subject: [PATCH 07/16] dropped useRef hook for old school variable --- .../view/markdownEditorView.tsx | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/components/markdownEditor/view/markdownEditorView.tsx b/src/components/markdownEditor/view/markdownEditorView.tsx index 3209cb961..9628aa2a7 100644 --- a/src/components/markdownEditor/view/markdownEditorView.tsx +++ b/src/components/markdownEditor/view/markdownEditorView.tsx @@ -54,6 +54,10 @@ import { walkthrough } from '../../../redux/constants/walkthroughConstants'; const MIN_BODY_INPUT_HEIGHT = 300; +//These variable keep track of body text input state, +//this helps keep load on minimal compared to both useState and useRef; +var bodyText = ''; +var bodySelection = {start: 0, end: 0}; const MarkdownEditorView = ({ draftBody, @@ -80,9 +84,6 @@ const MarkdownEditorView = ({ }) => { const dispatch = useDispatch(); - const bodyText = useRef(''); - const bodySelection = useRef({ start: 0, end: 0 }); - const [editable, setEditable] = useState(true); const [bodyInputHeight, setBodyInputHeight] = useState(MIN_BODY_INPUT_HEIGHT); const [isSnippetsOpen, setIsSnippetsOpen] = useState(false); @@ -104,7 +105,7 @@ const MarkdownEditorView = ({ useEffect(() => { if (!isPreviewActive) { - _setTextAndSelection({ selection: { start: 0, end: 0 }, text: bodyText.current }); + _setTextAndSelection({ selection: { start: 0, end: 0 }, text: bodyText }); } }, [isPreviewActive]); @@ -120,7 +121,7 @@ const MarkdownEditorView = ({ }, [onLoadDraftPress]); useEffect(() => { - if (bodyText.current === '' && draftBody !== '') { + if (bodyText === '' && draftBody !== '') { let draftBodyLength = draftBody.length; _setTextAndSelection({ selection: { start: draftBodyLength, end: draftBodyLength }, @@ -164,8 +165,8 @@ const MarkdownEditorView = ({ useEffect(() => { if (uploadedImage && uploadedImage.shouldInsert && !isUploading) { applyMediaLink({ - text: bodyText.current, - selection: bodySelection.current, + text: bodyText, + selection: bodySelection, setTextAndSelection: _setTextAndSelection, items: [{ url: uploadedImage.url, text: uploadedImage.hash }], }); @@ -177,7 +178,7 @@ const MarkdownEditorView = ({ }, [uploadedImage, isUploading]); useEffect(() => { - bodyText.current = draftBody; + bodyText = draftBody; }, [draftBody]); useEffect(() => { @@ -190,13 +191,13 @@ const MarkdownEditorView = ({ }, [autoFocusText]); useEffect(() => { - const nextText = bodyText.current.replace(bodyText.current, ''); + const nextText = bodyText.replace(bodyText, ''); if (nextText && nextText.length > 0) { - _changeText(bodyText.current); + _changeText(bodyText); } - }, [bodyText.current]); + }, [bodyText]); const changeUser = async () => { dispatch(toggleAccountsBottomSheet(!isVisibleAccountsBottomSheet)); @@ -204,8 +205,8 @@ const MarkdownEditorView = ({ const _onApplyUsername = (username) => { applyUsername({ - text: bodyText.current, - selection: bodySelection.current, + text: bodyText, + selection: bodySelection, setTextAndSelection: _setTextAndSelection, username, }); @@ -213,7 +214,7 @@ const MarkdownEditorView = ({ // eslint-disable-next-line react-hooks/exhaustive-deps const _changeText = useCallback((input) => { - bodyText.current = input; + bodyText = input; //NOTE: onChange method is called by direct parent of MarkdownEditor that is PostForm, do not remove if (onChange) { @@ -223,7 +224,7 @@ const MarkdownEditorView = ({ const _handleOnSelectionChange = async (event) => { - bodySelection.current = event.nativeEvent.selection; + bodySelection = event.nativeEvent.selection; }; const _handleOnContentSizeChange = async (event) => { @@ -233,13 +234,13 @@ const MarkdownEditorView = ({ // eslint-disable-next-line react-hooks/exhaustive-deps const _setTextAndSelection = useCallback(({ selection: _selection, text: _text }) => { - console.log('_text : ', _text); + // console.log('_text : ', _text); inputRef.current.setNativeProps({ text: _text, }); const _updateSelection = () => { - bodySelection.current = _selection + bodySelection = _selection inputRef.current.setNativeProps({ selection: _selection, }); @@ -263,8 +264,8 @@ const MarkdownEditorView = ({ const _renderPreview = () => ( - {bodyText.current ? ( - + {bodyText ? ( + ) : ( ... )} @@ -273,8 +274,8 @@ const MarkdownEditorView = ({ const _handleOnSnippetReceived = (snippetText) => { applySnippet({ - text: bodyText.current, - selection: bodySelection.current, + text: bodyText, + selection: bodySelection, setTextAndSelection: _setTextAndSelection, snippetText: `\n${snippetText}\n`, }); @@ -288,8 +289,8 @@ const MarkdownEditorView = ({ if (items.length) { applyMediaLink({ - text: bodyText.current, - selection: bodySelection.current, + text: bodyText, + selection: bodySelection, setTextAndSelection: _setTextAndSelection, items, }); @@ -298,8 +299,8 @@ const MarkdownEditorView = ({ const _handleOnAddLinkPress = () => { insertLinkModalRef.current?.showModal({ - selectedText: bodyText.current.slice(bodySelection.current.start, bodySelection.current.end), - selection: bodySelection.current, + selectedText: bodyText.slice(bodySelection.start, bodySelection.end), + selection: bodySelection, }); inputRef.current?.blur(); }; @@ -308,7 +309,7 @@ const MarkdownEditorView = ({ }; const _handleInsertLink = ({ snippetText, selection }) => { applySnippet({ - text: bodyText.current, + text: bodyText, selection, setTextAndSelection: _setTextAndSelection, snippetText, @@ -326,8 +327,8 @@ const MarkdownEditorView = ({ name={item.icon} onPress={() => item.onPress({ - text: bodyText.current, - selection: bodySelection.current, + text: bodyText, + selection: bodySelection, setTextAndSelection: _setTextAndSelection, item }) @@ -511,7 +512,7 @@ const MarkdownEditorView = ({ const _innerContent = ( <> {isAndroidOreo() ? _renderEditorWithoutScroll() : _renderEditorWithScroll()} - + {_renderFloatingDraftButton()} {!isPreviewActive && _renderEditorButtons()} From 8a292937514990964247a38885510389b394071f Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Wed, 8 Jun 2022 19:27:33 +0500 Subject: [PATCH 08/16] loading post options view into memory through method --- src/components/markdownEditor/view/markdownEditorView.tsx | 1 - src/screens/editor/children/postOptionsModal.tsx | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/markdownEditor/view/markdownEditorView.tsx b/src/components/markdownEditor/view/markdownEditorView.tsx index 9628aa2a7..6d302cf7c 100644 --- a/src/components/markdownEditor/view/markdownEditorView.tsx +++ b/src/components/markdownEditor/view/markdownEditorView.tsx @@ -7,7 +7,6 @@ import { Platform, ScrollView, TouchableOpacity, - Alert, } from 'react-native'; import { renderPostBody, postBodySummary } from '@ecency/render-helper'; import { useDispatch, useSelector } from 'react-redux'; diff --git a/src/screens/editor/children/postOptionsModal.tsx b/src/screens/editor/children/postOptionsModal.tsx index 3228e0190..2e1a04cf1 100644 --- a/src/screens/editor/children/postOptionsModal.tsx +++ b/src/screens/editor/children/postOptionsModal.tsx @@ -114,7 +114,7 @@ const PostOptionsModal = forwardRef(({ handleThumbSelection(index) } - const _renderContent = ( + const _renderContent = () => ( @@ -214,7 +214,7 @@ const PostOptionsModal = forwardRef(({ animationType="slide" style={styles.modalStyle} > - {_renderContent} + {_renderContent()} ); From b572b8b719aaa38c062dd3a654e524f7aeebcbf1 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Wed, 8 Jun 2022 19:34:03 +0500 Subject: [PATCH 09/16] resetting body text and selection upon new editor start --- .../markdownEditor/view/markdownEditorView.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/markdownEditor/view/markdownEditorView.tsx b/src/components/markdownEditor/view/markdownEditorView.tsx index 6d302cf7c..39335b6d6 100644 --- a/src/components/markdownEditor/view/markdownEditorView.tsx +++ b/src/components/markdownEditor/view/markdownEditorView.tsx @@ -102,6 +102,11 @@ const MarkdownEditorView = ({ const draftBtnTooltipRegistered = draftBtnTooltipState.get(walkthrough.EDITOR_DRAFT_BTN); const headerText = post && (post.summary || postBodySummary(post, 150, Platform.OS)); + useEffect(() => { + bodyText = ''; + bodySelection = {start:0, end:0}; + }, []); + useEffect(() => { if (!isPreviewActive) { _setTextAndSelection({ selection: { start: 0, end: 0 }, text: bodyText }); @@ -189,14 +194,7 @@ const MarkdownEditorView = ({ } }, [autoFocusText]); - useEffect(() => { - const nextText = bodyText.replace(bodyText, ''); - if (nextText && nextText.length > 0) { - _changeText(bodyText); - - } - }, [bodyText]); const changeUser = async () => { dispatch(toggleAccountsBottomSheet(!isVisibleAccountsBottomSheet)); From 2fae22c04978f7d48d70b14b90aacad12784ee75 Mon Sep 17 00:00:00 2001 From: noumantahir Date: Thu, 9 Jun 2022 13:13:06 +0500 Subject: [PATCH 10/16] fixed comment edit crash --- src/screens/editor/screen/editorScreen.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/screens/editor/screen/editorScreen.tsx b/src/screens/editor/screen/editorScreen.tsx index 7fe30db64..08adfd4a9 100644 --- a/src/screens/editor/screen/editorScreen.tsx +++ b/src/screens/editor/screen/editorScreen.tsx @@ -367,7 +367,6 @@ class EditorScreen extends Component { isLoggedIn, isPostSending, isReply, - quickReplyText, isUploading, post, uploadedImage, @@ -406,8 +405,7 @@ class EditorScreen extends Component { ); }; -console.log('fields :', fields); -console.log('quickReplyText : ', quickReplyText); + return ( @@ -451,7 +449,7 @@ console.log('quickReplyText : ', quickReplyText); )} Date: Thu, 9 Jun 2022 13:58:42 +0500 Subject: [PATCH 11/16] saving quick reply draft more frequently --- .../quickReplyModalContent.tsx | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index 9de1761cf..a7e8d43d6 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import EStyleSheet from 'react-native-extended-stylesheet'; import styles from './quickReplyModalStyles'; import { View, Text, Alert, TouchableOpacity, Keyboard, Platform } from 'react-native'; @@ -14,11 +14,12 @@ import { updateDraftCache, } from '../../redux/actions/cacheActions'; import { default as ROUTES } from '../../constants/routeNames'; -import get from 'lodash/get'; +import {get, debounce} from 'lodash'; import { navigate } from '../../navigation/service'; import { postBodySummary } from '@ecency/render-helper'; import { Draft } from '../../redux/reducers/cacheReducer'; import { RootState } from '../../redux/store/store'; +import comment from '../../constants/options/comment'; export interface QuickReplyModalContentProps { fetchPost?: any; @@ -73,13 +74,13 @@ export const QuickReplyModalContent = ({ }; // add quick comment value into cache - const _addQuickCommentIntoCache = () => { + const _addQuickCommentIntoCache = (value = commentValue) => { const date = new Date(); const updatedStamp = date.toISOString().substring(0, 19); const quickCommentDraftData: Draft = { author: currentAccount.name, - body: commentValue, + body: value, created: quickCommentDraft ? quickCommentDraft.created : updatedStamp, updated: updatedStamp, expiresAt: date.getTime() + 604800000, // 7 days expiry time @@ -88,10 +89,14 @@ export const QuickReplyModalContent = ({ //add quick comment cache entry dispatch(updateDraftCache(draftId, quickCommentDraftData)); }; + + // handle close press const _handleClosePress = () => { sheetModalRef.current?.setModalVisible(false); }; + + // navigate to post on summary press const _handleOnSummaryPress = () => { Keyboard.dismiss(); @@ -105,6 +110,7 @@ export const QuickReplyModalContent = ({ }); }; + // handle submit reply const _submitReply = async () => { let stateTimer; @@ -207,12 +213,23 @@ export const QuickReplyModalContent = ({ params: { isReply: true, post: selectedPost, - quickReplyText: commentValue, + draftId: draftId, fetchPost, }, }); } }; + + + const _deboucedCacheUpdate = useCallback(debounce(_addQuickCommentIntoCache, 500),[]) + + const _onChangeText = (value) => { + setCommentValue(value); + _deboucedCacheUpdate(value) + } + + + //VIEW_RENDERERS const _renderSheetHeader = () => ( @@ -284,9 +301,7 @@ export const QuickReplyModalContent = ({ { - setCommentValue(value); - }} + onChangeText={_onChangeText} value={commentValue} // autoFocus placeholder={intl.formatMessage({ From 2fd9c414216ff280643c1c1b39509cebbd2d9487 Mon Sep 17 00:00:00 2001 From: noumantahir Date: Thu, 9 Jun 2022 14:00:45 +0500 Subject: [PATCH 12/16] loading and saving comment draft based on passed draft id --- .../editor/container/editorContainer.tsx | 48 ++++++++++++++----- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/src/screens/editor/container/editorContainer.tsx b/src/screens/editor/container/editorContainer.tsx index 1f7c7893f..36cb92d09 100644 --- a/src/screens/editor/container/editorContainer.tsx +++ b/src/screens/editor/container/editorContainer.tsx @@ -46,7 +46,7 @@ import EditorScreen from '../screen/editorScreen'; import bugsnapInstance from '../../../config/bugsnag'; import { removeBeneficiaries, setBeneficiaries } from '../../../redux/actions/editorActions'; import { TEMP_BENEFICIARIES_ID } from '../../../redux/constants/constants'; -import { updateCommentCache } from '../../../redux/actions/cacheActions'; +import { updateCommentCache, updateDraftCache } from '../../../redux/actions/cacheActions'; /* * Props Name Description Value @@ -90,7 +90,7 @@ class EditorContainer extends Component { const { currentAccount, navigation } = this.props; const username = currentAccount && currentAccount.name ? currentAccount.name : ''; let isReply; - let quickReplyText; + let draftId; let isEdit; let post; let _draft; @@ -133,12 +133,15 @@ class EditorContainer extends Component { } if (navigationParams.isReply) { - ({ isReply, quickReplyText } = navigationParams); + ({ isReply, draftId } = navigationParams); this.setState({ isReply, - quickReplyText, + draftId, autoFocusText: true, }); + if(draftId){ + this._getStorageDraft(username, isReply, {_id:draftId}); + } } if (navigationParams.isEdit) { @@ -158,7 +161,7 @@ class EditorContainer extends Component { } } - if (!isEdit && !_draft && !hasSharedIntent) { + if (!isEdit && !_draft && !draftId && !hasSharedIntent) { this._fetchDraftsForComparison(isReply); } this._requestKeyboardFocus(); @@ -195,16 +198,18 @@ class EditorContainer extends Component { _getStorageDraft = async (username, isReply, paramDraft) => { if (isReply) { - const draftReply = await AsyncStorage.getItem('temp-reply'); - - if (draftReply) { + //TODO: get draft reply from redux based on draft passed in param; + const {drafts} = this.props; + const draft = drafts.get(paramDraft._id); + if (draft && draft.body) { this.setState({ draftPost: { - body: draftReply, + body: draft.body, }, }); } } else { + //TOOD: get draft from redux after reply side is complete getDraftPost(username, paramDraft && paramDraft._id).then((result) => { //if result is return and param draft available, compare timestamp, use latest //if no draft, use result anayways @@ -523,9 +528,14 @@ class EditorContainer extends Component { }; _saveDraftToDB = async (fields, saveAsNew = false) => { - const { isDraftSaved, draftId, thumbIndex } = this.state; + const { isDraftSaved, draftId, thumbIndex, isReply } = this.state; const { currentAccount, dispatch, intl } = this.props; + if(isReply){ + return; + } + + const beneficiaries = this._extractBeneficiaries(); try { @@ -636,7 +646,20 @@ class EditorContainer extends Component { //save reply data if (isReply && draftField.body !== null) { - await AsyncStorage.setItem('temp-reply', draftField.body); + //TODO: update draft item in redux using draftId passed in params + const {dispatch} = this.props; + const {draftId} = this.state; + + const replyDraft = { + author:currentAccount.name, + body:fields.body, + updated: new Date().toISOString().substring(0, 19), + expiresAt: new Date().getTime() + 604800000, // 7 days expiry time + } + + dispatch(updateDraftCache(draftId, replyDraft)) + //await AsyncStorage.setItem('temp-reply', draftField.body); + //save existing draft data locally } else if (draftId) { @@ -1271,7 +1294,8 @@ const mapStateToProps = (state) => ({ isDefaultFooter: state.account.isDefaultFooter, isLoggedIn: state.application.isLoggedIn, pinCode: state.application.pin, - beneficiariesMap: state.editor.beneficiariesMap + beneficiariesMap: state.editor.beneficiariesMap, + drafts: state.cache.drafts, }); export default connect(mapStateToProps)(injectIntl(EditorContainer)); From 6b859aaaeaa76b05011cd7d293e17b4c735fc723 Mon Sep 17 00:00:00 2001 From: noumantahir Date: Thu, 9 Jun 2022 15:29:46 +0500 Subject: [PATCH 13/16] cleaned up draft catch update method arguments --- .../quickReplyModalContent.tsx | 7 +- src/redux/actions/cacheActions.ts | 147 +++++++++--------- src/redux/reducers/cacheReducer.ts | 21 ++- 3 files changed, 90 insertions(+), 85 deletions(-) diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index a7e8d43d6..b39d3e8cb 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -75,15 +75,10 @@ export const QuickReplyModalContent = ({ // add quick comment value into cache const _addQuickCommentIntoCache = (value = commentValue) => { - const date = new Date(); - const updatedStamp = date.toISOString().substring(0, 19); const quickCommentDraftData: Draft = { author: currentAccount.name, - body: value, - created: quickCommentDraft ? quickCommentDraft.created : updatedStamp, - updated: updatedStamp, - expiresAt: date.getTime() + 604800000, // 7 days expiry time + body: value }; //add quick comment cache entry diff --git a/src/redux/actions/cacheActions.ts b/src/redux/actions/cacheActions.ts index 49a9ac432..3d16366cb 100644 --- a/src/redux/actions/cacheActions.ts +++ b/src/redux/actions/cacheActions.ts @@ -2,93 +2,92 @@ import { renderPostBody } from '@ecency/render-helper'; import { Platform } from 'react-native'; import { makeJsonMetadataReply } from '../../utils/editor'; import { - UPDATE_VOTE_CACHE, - PURGE_EXPIRED_CACHE, - UPDATE_COMMENT_CACHE, - DELETE_COMMENT_CACHE_ENTRY, - UPDATE_DRAFT_CACHE, - DELETE_DRAFT_CACHE_ENTRY, - } from '../constants/constants'; + UPDATE_VOTE_CACHE, + PURGE_EXPIRED_CACHE, + UPDATE_COMMENT_CACHE, + DELETE_COMMENT_CACHE_ENTRY, + UPDATE_DRAFT_CACHE, + DELETE_DRAFT_CACHE_ENTRY, +} from '../constants/constants'; import { Comment, Draft, Vote } from '../reducers/cacheReducer'; - - - export const updateVoteCache = (postPath:string, vote:Vote) => ({ - payload:{ - postPath, - vote - }, - type: UPDATE_VOTE_CACHE - }) - interface CommentCacheOptions { - isUpdate?:boolean; - parentTags?:Array; - } +export const updateVoteCache = (postPath: string, vote: Vote) => ({ + payload: { + postPath, + vote + }, + type: UPDATE_VOTE_CACHE +}) - export const updateCommentCache = (commentPath:string, comment:Comment, options:CommentCacheOptions = {isUpdate:false}) => { - console.log("body received:", comment.markdownBody); - const updated = new Date(); - updated.setSeconds(updated.getSeconds() - 5); //make cache delayed by 5 seconds to avoid same updated stamp in post data - const updatedStamp = updated.toISOString().substring(0, 19); //server only return 19 character time string without timezone part - - if(options.isUpdate && !comment.created){ - throw new Error("For comment update, created prop must be provided from original comment data to update local cache"); - } +interface CommentCacheOptions { + isUpdate?: boolean; + parentTags?: Array; +} - if(!options.parentTags && !comment.json_metadata){ - throw new Error("either of json_metadata in comment data or parentTags in options must be provided"); - } +export const updateCommentCache = (commentPath: string, comment: Comment, options: CommentCacheOptions = { isUpdate: false }) => { - comment.created = comment.created || updatedStamp; //created will be set only once for new comment; - comment.updated = comment.updated || updatedStamp; - comment.expiresAt = comment.expiresAt || updated.getTime() + 6000000;//600000; - comment.active_votes = comment.active_votes || []; - comment.net_rshares = comment.net_rshares || 0; - comment.author_reputation = comment.author_reputation || 25; - comment.total_payout = comment.total_payout || 0; - comment.json_metadata = comment.json_metadata || makeJsonMetadataReply(options.parentTags) - comment.isDeletable = comment.isDeletable || true; + console.log("body received:", comment.markdownBody); + const updated = new Date(); + updated.setSeconds(updated.getSeconds() - 5); //make cache delayed by 5 seconds to avoid same updated stamp in post data + const updatedStamp = updated.toISOString().substring(0, 19); //server only return 19 character time string without timezone part - comment.body = renderPostBody({ - author:comment.author, - permlink:comment.permlink, - last_update:comment.updated, - body:comment.markdownBody, - }, true, Platform.OS === 'android'); + if (options.isUpdate && !comment.created) { + throw new Error("For comment update, created prop must be provided from original comment data to update local cache"); + } - return ({ - payload:{ - commentPath, - comment - }, - type: UPDATE_COMMENT_CACHE - }) - } + if (!options.parentTags && !comment.json_metadata) { + throw new Error("either of json_metadata in comment data or parentTags in options must be provided"); + } - export const deleteCommentCacheEntry = (commentPath:string) => ({ - payload:commentPath, - type: DELETE_COMMENT_CACHE_ENTRY - }) - - export const updateDraftCache = (id:string, draft:Draft) => ({ - payload:{ - id, - draft + comment.created = comment.created || updatedStamp; //created will be set only once for new comment; + comment.updated = comment.updated || updatedStamp; + comment.expiresAt = comment.expiresAt || updated.getTime() + 6000000;//600000; + comment.active_votes = comment.active_votes || []; + comment.net_rshares = comment.net_rshares || 0; + comment.author_reputation = comment.author_reputation || 25; + comment.total_payout = comment.total_payout || 0; + comment.json_metadata = comment.json_metadata || makeJsonMetadataReply(options.parentTags) + comment.isDeletable = comment.isDeletable || true; + + comment.body = renderPostBody({ + author: comment.author, + permlink: comment.permlink, + last_update: comment.updated, + body: comment.markdownBody, + }, true, Platform.OS === 'android'); + + return ({ + payload: { + commentPath, + comment }, - type: UPDATE_DRAFT_CACHE + type: UPDATE_COMMENT_CACHE }) +} - export const deleteDraftCacheEntry = (id:string) => ({ - payload:id, - type: DELETE_DRAFT_CACHE_ENTRY - }) +export const deleteCommentCacheEntry = (commentPath: string) => ({ + payload: commentPath, + type: DELETE_COMMENT_CACHE_ENTRY +}) + +export const updateDraftCache = (id: string, draft: Draft) => ({ + payload: { + id, + draft + }, + type: UPDATE_DRAFT_CACHE +}) + +export const deleteDraftCacheEntry = (id: string) => ({ + payload: id, + type: DELETE_DRAFT_CACHE_ENTRY +}) + +export const purgeExpiredCache = () => ({ + type: PURGE_EXPIRED_CACHE +}) - export const purgeExpiredCache = () => ({ - type: PURGE_EXPIRED_CACHE - }) - - \ No newline at end of file diff --git a/src/redux/reducers/cacheReducer.ts b/src/redux/reducers/cacheReducer.ts index 055ef3c83..6e5ff76c6 100644 --- a/src/redux/reducers/cacheReducer.ts +++ b/src/redux/reducers/cacheReducer.ts @@ -28,10 +28,12 @@ export interface Comment { export interface Draft { author: string, - body?:string, - created?:string, - updated?:string, - expiresAt:number; + body:string, + title?:string, + tags?:string, + created?:number, + updated?:number, + expiresAt?:number; } interface State { @@ -93,7 +95,16 @@ const initialState:State = { if(!state.drafts){ state.drafts = new Map(); } - state.drafts.set(payload.id, payload.draft); + + const curTime = new Date().getTime(); + const curDraft = state.drafts.get(payload.id); + const payloadDraft = payload.draft; + + payloadDraft.created = curDraft ? curDraft.created : curTime; + payloadDraft.updated = curTime; + payloadDraft.expiresAt = curTime + 604800000 // 7 days ms + + state.drafts.set(payload.id, payloadDraft); return { ...state, //spread operator in requried here, otherwise persist do not register change lastUpdate: { From 12a4f1e7af49b78deba932c8b297420dd9ec5fc6 Mon Sep 17 00:00:00 2001 From: noumantahir Date: Thu, 9 Jun 2022 15:30:16 +0500 Subject: [PATCH 14/16] replaced setDraftPost, getDraftPost with redux based drafts --- src/redux/constants/constants.js | 1 + .../editor/container/editorContainer.tsx | 181 +++++++----------- 2 files changed, 74 insertions(+), 108 deletions(-) diff --git a/src/redux/constants/constants.js b/src/redux/constants/constants.js index fe59fc12d..2c3c0b1b9 100644 --- a/src/redux/constants/constants.js +++ b/src/redux/constants/constants.js @@ -113,6 +113,7 @@ export const UPDATE_COMMENT_CACHE = 'UPDATE_COMMENT_CACHE'; export const DELETE_COMMENT_CACHE_ENTRY = 'DELETE_COMMENT_CACHE_ENTRY'; export const UPDATE_DRAFT_CACHE = 'UPDATE_DRAFT_CACHE'; export const DELETE_DRAFT_CACHE_ENTRY = 'DELETE_DRAFT_CACHE_ENTRY'; +export const DEFAULT_USER_DRAFT_ID = 'DEFAULT_USER_DRAFT_ID_'; // TOOLTIPS export const REGISTER_TOOLTIP = 'REGISTER_TOOLTIP'; diff --git a/src/screens/editor/container/editorContainer.tsx b/src/screens/editor/container/editorContainer.tsx index 36cb92d09..67095c2de 100644 --- a/src/screens/editor/container/editorContainer.tsx +++ b/src/screens/editor/container/editorContainer.tsx @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { injectIntl } from 'react-intl'; -import { Alert, Platform } from 'react-native'; +import { Alert } from 'react-native'; import ImagePicker from 'react-native-image-crop-picker'; import get from 'lodash/get'; import AsyncStorage from '@react-native-community/async-storage'; @@ -25,7 +25,6 @@ import { reblog, postComment, } from '../../../providers/hive/dhive'; -import { setDraftPost, getDraftPost } from '../../../realm/realm'; // Constants import { default as ROUTES } from '../../../constants/routeNames'; @@ -45,8 +44,8 @@ import { import EditorScreen from '../screen/editorScreen'; import bugsnapInstance from '../../../config/bugsnag'; import { removeBeneficiaries, setBeneficiaries } from '../../../redux/actions/editorActions'; -import { TEMP_BENEFICIARIES_ID } from '../../../redux/constants/constants'; -import { updateCommentCache, updateDraftCache } from '../../../redux/actions/cacheActions'; +import { DEFAULT_USER_DRAFT_ID, TEMP_BENEFICIARIES_ID } from '../../../redux/constants/constants'; +import { deleteDraftCacheEntry, updateCommentCache, updateDraftCache } from '../../../redux/actions/cacheActions'; /* * Props Name Description Value @@ -139,8 +138,8 @@ class EditorContainer extends Component { draftId, autoFocusText: true, }); - if(draftId){ - this._getStorageDraft(username, isReply, {_id:draftId}); + if (draftId) { + this._getStorageDraft(username, isReply, { _id: draftId }); } } @@ -197,51 +196,57 @@ class EditorContainer extends Component { } _getStorageDraft = async (username, isReply, paramDraft) => { + const { drafts } = this.props; + if (isReply) { - //TODO: get draft reply from redux based on draft passed in param; - const {drafts} = this.props; - const draft = drafts.get(paramDraft._id); - if (draft && draft.body) { + const _draft = drafts.get(paramDraft._id); + if (_draft && _draft.body) { this.setState({ draftPost: { - body: draft.body, + body: _draft.body, }, }); } } else { //TOOD: get draft from redux after reply side is complete - getDraftPost(username, paramDraft && paramDraft._id).then((result) => { - //if result is return and param draft available, compare timestamp, use latest - //if no draft, use result anayways - if (result && (!paramDraft || paramDraft.timestamp < result.timestamp)) { - this.setState({ - draftPost: { - body: get(result, 'body', ''), - title: get(result, 'title', ''), - tags: get(result, 'tags', '').split(','), - isDraft: paramDraft ? true : false, - draftId: paramDraft ? paramDraft._id : null, - }, - }); - } + const _draftId = paramDraft ? paramDraft._id : DEFAULT_USER_DRAFT_ID + username; + const _localDraft = drafts.get(_draftId); + if (!_localDraft) { + return; + } - //if above fails with either no result returned or timestamp is old, - // and use draft form nav param if available. - else if (paramDraft) { - const _tags = paramDraft.tags.includes(' ') - ? paramDraft.tags.split(' ') - : paramDraft.tags.split(','); - this.setState({ - draftPost: { - title: paramDraft.title, - body: paramDraft.body, - tags: _tags, - }, - isDraft: true, - draftId: paramDraft._id, - }); - } - }); + //if _draft is returned and param draft is available, compare timestamp, use latest + //if no draft, use result anayways + + if (_localDraft && (!paramDraft || paramDraft.timestamp < _localDraft.updated)) { + this.setState({ + draftPost: { + body: get(_localDraft, 'body', ''), + title: get(_localDraft, 'title', ''), + tags: get(_localDraft, 'tags', '').split(','), + isDraft: paramDraft ? true : false, + draftId: paramDraft ? paramDraft._id : null, + }, + }); + } + + //if above fails with either no result returned or timestamp is old, + // and use draft form nav param if available. + else if (paramDraft) { + const _tags = paramDraft.tags.includes(' ') + ? paramDraft.tags.split(' ') + : paramDraft.tags.split(','); + this.setState({ + draftPost: { + title: paramDraft.title, + body: paramDraft.body, + tags: _tags, + }, + isDraft: true, + draftId: paramDraft._id, + }); + } + ; } }; @@ -256,14 +261,14 @@ class EditorContainer extends Component { }; /** - * this fucntion is run if editor is access used mid tab or reply section + * this fucntion is run if editor is access fused mid tab or reply section * it fetches fresh drafts and run some comparions to load one of following * empty editor, load non-remote draft or most recent remote draft based on timestamps * prompts user as well * @param isReply **/ _fetchDraftsForComparison = async (isReply) => { - const { currentAccount, isLoggedIn, intl, dispatch } = this.props; + const { currentAccount, isLoggedIn, intl, dispatch, drafts } = this.props; const username = get(currentAccount, 'name', ''); //initilizes editor with reply or non remote id less draft @@ -287,28 +292,29 @@ class EditorContainer extends Component { return; } - const drafts = await getDrafts(username); - const idLessDraft = await getDraftPost(username); + const remoteDrafts = await getDrafts(username); + + const idLessDraft = drafts.get(DEFAULT_USER_DRAFT_ID + username) const loadRecentDraft = () => { //if no draft available means local draft is recent - if (drafts.length == 0) { + if (remoteDrafts.length == 0) { _getStorageDraftGeneral(false); return; } //sort darts based on timestamps - drafts.sort((d1, d2) => + remoteDrafts.sort((d1, d2) => new Date(d1.modified).getTime() < new Date(d2.modified).getTime() ? 1 : -1, ); - const _draft = drafts[0]; + const _draft = remoteDrafts[0]; //if unsaved local draft is more latest then remote draft, use that instead //if editor was opened from draft screens, this code will be skipped anyways. if ( idLessDraft && (idLessDraft.title !== '' || idLessDraft.tags !== '' || idLessDraft.body !== '') && - new Date(_draft.modified).getTime() < idLessDraft.timestamp + new Date(_draft.modified).getTime() < idLessDraft.updated ) { _getStorageDraftGeneral(false); return; @@ -322,7 +328,7 @@ class EditorContainer extends Component { this._getStorageDraft(username, isReply, _draft); }; - if (drafts.length > 0 || (idLessDraft && idLessDraft.timestamp > 0)) { + if (remoteDrafts.length > 0 || (idLessDraft && idLessDraft.updated > 0)) { this.setState({ onLoadDraftPress: loadRecentDraft, }); @@ -531,7 +537,7 @@ class EditorContainer extends Component { const { isDraftSaved, draftId, thumbIndex, isReply } = this.state; const { currentAccount, dispatch, intl } = this.props; - if(isReply){ + if (isReply) { return; } @@ -584,16 +590,8 @@ class EditorContainer extends Component { //clear local copy if darft save is successful const username = get(currentAccount, 'name', ''); - setDraftPost( - { - title: '', - body: '', - tags: '', - timestamp: 0, - }, - username, - saveAsNew ? draftId : undefined - ); + + dispatch(deleteDraftCacheEntry(draftId || (DEFAULT_USER_DRAFT_ID + username))) } @@ -636,39 +634,28 @@ class EditorContainer extends Component { return; } - const { currentAccount } = this.props; + const { currentAccount, dispatch } = this.props; const username = currentAccount && currentAccount.name ? currentAccount.name : ''; const draftField = { - ...fields, + title: fields.title, + body: fields.body, tags: fields.tags && fields.tags.length > 0 ? fields.tags.toString() : '', - }; + author: username, + } //save reply data if (isReply && draftField.body !== null) { - //TODO: update draft item in redux using draftId passed in params - const {dispatch} = this.props; - const {draftId} = this.state; - - const replyDraft = { - author:currentAccount.name, - body:fields.body, - updated: new Date().toISOString().substring(0, 19), - expiresAt: new Date().getTime() + 604800000, // 7 days expiry time - } - - dispatch(updateDraftCache(draftId, replyDraft)) - //await AsyncStorage.setItem('temp-reply', draftField.body); - + dispatch(updateDraftCache(draftId, draftField)) //save existing draft data locally } else if (draftId) { - setDraftPost(draftField, username, draftId); + dispatch(updateDraftCache(draftId, draftField)) } //update editor data locally else if (!isReply) { - setDraftPost(draftField, username); + dispatch(updateDraftCache(DEFAULT_USER_DRAFT_ID + username, draftField)); } }; @@ -767,15 +754,7 @@ class EditorContainer extends Component { } //post publish updates - setDraftPost( - { - title: '', - body: '', - tags: '', - timestamp: 0, - }, - currentAccount.name, - ); + dispatch(deleteDraftCacheEntry(DEFAULT_USER_DRAFT_ID + currentAccount.name)) dispatch(removeBeneficiaries(TEMP_BENEFICIARIES_ID)) if (draftId) { @@ -1162,15 +1141,8 @@ class EditorContainer extends Component { }), ), ); - setDraftPost( - { - title: '', - body: '', - tags: '', - timestamp: 0, - }, - currentAccount.name, - ); + + dispatch(deleteDraftCacheEntry(DEFAULT_USER_DRAFT_ID + currentAccount.name)) setTimeout(() => { navigation.replace(ROUTES.SCREENS.DRAFTS, @@ -1190,17 +1162,10 @@ class EditorContainer extends Component { _initialEditor = () => { const { currentAccount: { name }, + dispatch } = this.props; - setDraftPost( - { - title: '', - body: '', - tags: '', - timestamp: 0, - }, - name, - ); + dispatch(deleteDraftCacheEntry(DEFAULT_USER_DRAFT_ID + name)) this.setState({ uploadedImage: null, From b7928234e7056b110cf37c147d4f65154ac58a5f Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Thu, 9 Jun 2022 17:46:07 +0500 Subject: [PATCH 15/16] removed dependency of draftId from navigation params for reply cache --- src/components/quickReplyModal/quickReplyModalContent.tsx | 4 +--- src/screens/editor/container/editorContainer.tsx | 6 +++++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/quickReplyModal/quickReplyModalContent.tsx b/src/components/quickReplyModal/quickReplyModalContent.tsx index b39d3e8cb..d829d031e 100644 --- a/src/components/quickReplyModal/quickReplyModalContent.tsx +++ b/src/components/quickReplyModal/quickReplyModalContent.tsx @@ -44,7 +44,7 @@ export const QuickReplyModalContent = ({ const [commentValue, setCommentValue] = useState(''); const [isSending, setIsSending] = useState(false); - const [quickCommentDraft, setQuickCommentDraft] = useState(null); + const headerText = selectedPost && (selectedPost.summary || postBodySummary(selectedPost, 150, Platform.OS as any)); @@ -61,7 +61,6 @@ export const QuickReplyModalContent = ({ if (drafts.has(draftId) && currentAccount.name === drafts.get(draftId).author) { const quickComment: Draft = drafts.get(draftId); setCommentValue(quickComment.body); - setQuickCommentDraft(quickComment); } else { setCommentValue(''); } @@ -208,7 +207,6 @@ export const QuickReplyModalContent = ({ params: { isReply: true, post: selectedPost, - draftId: draftId, fetchPost, }, }); diff --git a/src/screens/editor/container/editorContainer.tsx b/src/screens/editor/container/editorContainer.tsx index 67095c2de..79a47fbfc 100644 --- a/src/screens/editor/container/editorContainer.tsx +++ b/src/screens/editor/container/editorContainer.tsx @@ -132,7 +132,11 @@ class EditorContainer extends Component { } if (navigationParams.isReply) { - ({ isReply, draftId } = navigationParams); + ({ isReply } = navigationParams); + if(post){ + draftId = `${currentAccount.name}/${post.author}/${post.permlink}` + } + this.setState({ isReply, draftId, From 1fd71decb85603e91af02f9655dd70935022eb98 Mon Sep 17 00:00:00 2001 From: Nouman Tahir Date: Thu, 9 Jun 2022 17:55:40 +0500 Subject: [PATCH 16/16] fixed issue with remote draft not loading --- src/screens/editor/container/editorContainer.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/screens/editor/container/editorContainer.tsx b/src/screens/editor/container/editorContainer.tsx index 79a47fbfc..573a94a57 100644 --- a/src/screens/editor/container/editorContainer.tsx +++ b/src/screens/editor/container/editorContainer.tsx @@ -215,9 +215,6 @@ class EditorContainer extends Component { //TOOD: get draft from redux after reply side is complete const _draftId = paramDraft ? paramDraft._id : DEFAULT_USER_DRAFT_ID + username; const _localDraft = drafts.get(_draftId); - if (!_localDraft) { - return; - } //if _draft is returned and param draft is available, compare timestamp, use latest //if no draft, use result anayways