Merge pull request #1887 from ecency/nt/post-draft-master

Nt/post draft master
This commit is contained in:
Feruz M 2021-03-31 22:20:32 +03:00 committed by GitHub
commit 8c214a0927
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 598 additions and 401 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,22 +1,22 @@
PODS:
- appcenter-analytics (3.1.0):
- AppCenter/Analytics
- AppCenterReactNativeShared
- React
- appcenter-core (3.1.0):
- AppCenterReactNativeShared
- React
- appcenter-crashes (3.1.0):
- AppCenter/Crashes
- AppCenterReactNativeShared
- React
- AppCenter/Analytics (3.3.4):
- appcenter-analytics (4.1.0):
- AppCenter/Analytics (~> 4.0)
- AppCenterReactNativeShared (~> 4.0)
- React-Core
- appcenter-core (4.1.0):
- AppCenterReactNativeShared (~> 4.0)
- React-Core
- appcenter-crashes (4.1.0):
- AppCenter/Crashes (~> 4.0)
- AppCenterReactNativeShared (~> 4.0)
- React-Core
- AppCenter/Analytics (4.1.1):
- AppCenter/Core
- AppCenter/Core (3.3.4)
- AppCenter/Crashes (3.3.4):
- AppCenter/Core (4.1.1)
- AppCenter/Crashes (4.1.1):
- AppCenter/Core
- AppCenterReactNativeShared (3.1.2):
- AppCenter/Core (= 3.3.4)
- AppCenterReactNativeShared (4.1.0):
- AppCenter/Core (= 4.1.1)
- boost-for-react-native (1.63.0)
- BugsnagReactNative (2.23.10):
- BugsnagReactNative/Core (= 2.23.10)
@ -641,11 +641,11 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/yoga"
SPEC CHECKSUMS:
AppCenter: 0487be185c038ed2ecc5ed38219af1dcd280af23
appcenter-analytics: cca12b5d07593a309e99db475f6da7f1a61ae694
appcenter-core: cd5eaa48cfc6dd87dad2912d6718f21f4955327f
appcenter-crashes: 369cc9160392c137b791304ee96957f28ef143d1
AppCenterReactNativeShared: 66667cd2dfad295b84bbf0f462e0e139e2128284
AppCenter: cd53e3ed3563cc720bcb806c9731a12389b40d44
appcenter-analytics: aa074250b2cb182d6f7d67d9c236dd4606673ab5
appcenter-core: c97920e79eba2321a262d3733cb2a6f1097db539
appcenter-crashes: 804ae51c5d1742ce20fdb6a68b3d06208a404ad5
AppCenterReactNativeShared: 2d5a53b6cbd2fe7f63f0033e9f0a40757a4eacf5
boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c
BugsnagReactNative: 98fb350df4bb0c94cce903023531a1a5cc11fa51
BVLinearGradient: e3aad03778a456d77928f594a649e96995f1c872

View File

@ -45,9 +45,9 @@
"@react-native-firebase/app": "^8.4.7",
"@react-native-firebase/dynamic-links": "^7.4.2",
"@react-native-firebase/messaging": "^7.4.2",
"appcenter": "3.1.0",
"appcenter-analytics": "3.1.0",
"appcenter-crashes": "3.1.0",
"appcenter": "^4.1.0",
"appcenter-analytics": "^4.1.0",
"appcenter-crashes": "^4.1.0",
"axios": "^0.21.1",
"buffer": "^5.4.3",
"bugsnag-react-native": "^2.23.10",

View File

@ -1,4 +1,5 @@
import EStyleSheet from 'react-native-extended-stylesheet';
import { Platform } from 'react-native';
export default EStyleSheet.create({
container: {
@ -9,12 +10,12 @@ export default EStyleSheet.create({
textWrapper: {
fontSize: 12,
paddingTop: 16,
paddingBottom: 0, // On android side, textinput has default padding
paddingBottom: Platform.OS === 'ios' ? 32 : 0, // On android side, textinput has default padding
paddingHorizontal: 16,
color: '$primaryBlack',
backgroundColor: '$primaryBackgroundColor',
// fontFamily: '$editorFont',
// textAlignVertical: 'top',
minHeight: 200,
textAlignVertical: 'top',
},
previewContainer: {
flex: 1,

View File

@ -41,6 +41,8 @@ import { ThemeContainer } from '../../../containers';
import styles from './markdownEditorStyles';
import applySnippet from './formats/applySnippet';
const MIN_BODY_INPUT_HEIGHT = 200;
const MarkdownEditorView = ({
draftBody,
handleIsFormValid,
@ -66,7 +68,7 @@ const MarkdownEditorView = ({
const [text, setText] = useState(draftBody || '');
const [selection, setSelection] = useState({ start: 0, end: 0 });
const [editable, setEditable] = useState(true);
const [height, setHeight] = useState(0);
const [bodyInputHeight, setBodyInputHeight] = useState(MIN_BODY_INPUT_HEIGHT);
const [isSnippetsOpen, setIsSnippetsOpen] = useState(false);
const inputRef = useRef(null);
@ -161,6 +163,11 @@ const MarkdownEditorView = ({
setSelection(event.nativeEvent.selection);
};
const _handleOnContentSizeChange = async (event) => {
const height = Math.max(MIN_BODY_INPUT_HEIGHT, event.nativeEvent.contentSize.height + 30);
setBodyInputHeight(height);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
const _setTextAndSelection = useCallback(({ selection: _selection, text: _text }) => {
inputRef.current.setNativeProps({
@ -337,13 +344,14 @@ const MarkdownEditorView = ({
})}
placeholderTextColor={isDarkTheme ? '#526d91' : '#c1c5c7'}
selectionColor="#357ce6"
style={styles.textWrapper}
style={{ ...styles.textWrapper, height: bodyInputHeight }}
underlineColorAndroid="transparent"
innerRef={inputRef}
editable={editable}
contextMenuHidden={false}
autoGrow={false}
scrollEnabled={false}
onContentSizeChange={_handleOnContentSizeChange}
/>
)}
</ThemeContainer>

View File

@ -301,7 +301,19 @@
"my_communities": "My Communities",
"top_communities": "Top Communities",
"schedule_modal_title": "Schedule Post",
"snippets": "Snippets"
"snippets": "Snippets",
"alert_init_title":"Initialize",
"alert_init_body":"Load recent draft or create new post",
"alert_btn_draft":"Load Draft",
"alert_btn_new":"New Post",
"alert_pub_edit_title":"Publishing edits",
"alert_pub_new_title":"Publishing new post",
"alert_pub_body":"Are you sure?",
"alert_btn_yes":"Yes",
"alert_btn_no":"No",
"draft_save_success":"Draft Saved",
"draft_save_fail":"Failed to save draft"
},
"snippets":{
"label_no_snippets":"No Snippets Found",
@ -406,7 +418,8 @@
"title": "Drafts",
"load_error": "Could not load drafts",
"empty_list": "Nothing here",
"deleted": "Draft deleted"
"deleted": "Draft deleted",
},
"schedules": {
"title": "Schedules",

View File

@ -79,6 +79,8 @@ export const addDraft = (data) =>
const { drafts } = res.data;
if (drafts) {
resolve(drafts.pop());
} else {
reject(new Error('No drafts returned in response'));
}
})
.catch((error) => {
@ -102,7 +104,11 @@ export const updateDraft = (data) =>
tags: data.tags,
})
.then((res) => {
if (res.data) {
resolve(res.data);
} else {
reject(new Error('No data retuend in update response'));
}
})
.catch((error) => {
bugsnag.notify(error);

View File

@ -118,19 +118,31 @@ export const removeAllSCAccounts = async () => {
}
};
export const setDraftPost = async (fields, username) => {
export const setDraftPost = async (fields, username, draftId) => {
try {
let draft = await getItemFromStorage(DRAFT_SCHEMA);
let timestamp = new Date().getTime();
const data = {
username,
draftId,
timestamp,
title: fields.title,
tags: fields.tags,
body: fields.body,
};
if (draft && draft.some((e) => e.username === username)) {
draft = draft.map((item) => (item.username === username ? { ...item, ...data } : item));
//check if entry esist
const draftIndex = draft.findIndex(
(item) => draftId === undefined || item.draftId === draftId,
);
if (draftIndex < 0) {
draft.push(data);
} else {
draft[draftIndex] = data;
}
} else {
draft = [];
draft.push(data);
@ -142,10 +154,13 @@ export const setDraftPost = async (fields, username) => {
}
};
export const getDraftPost = async (username) => {
export const getDraftPost = async (username, draftId) => {
try {
const draft = await getItemFromStorage(DRAFT_SCHEMA);
const draftObj = draft.filter((item) => item.username === username);
const draftObj = draft.filter(
(item) => item.username === username && (draftId === undefined || item.draftId === draftId),
);
return draftObj[0];
} catch (error) {
return error;

View File

@ -22,7 +22,7 @@ const persistedReducer = persistReducer(persistConfig, reducer);
const middleware = [thunk];
if (process.env.NODE_ENV === 'development') {
middleware.push(logger);
// middleware.push(logger);
}
const store = createStore(persistedReducer, applyMiddleware(...middleware));

View File

@ -8,7 +8,13 @@ import AsyncStorage from '@react-native-community/async-storage';
// Services and Actions
import { Buffer } from 'buffer';
import { uploadImage, addDraft, updateDraft, schedule } from '../../../providers/ecency/ecency';
import {
uploadImage,
addDraft,
updateDraft,
schedule,
getDrafts,
} from '../../../providers/ecency/ecency';
import { toastNotification, setRcOffer } from '../../../redux/actions/uiAction';
import {
postContent,
@ -42,6 +48,8 @@ import EditorScreen from '../screen/editorScreen';
*/
class EditorContainer extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
@ -63,7 +71,102 @@ class EditorContainer extends Component {
};
}
_getDraft = async (username, isReply) => {
// Component Life Cycle Functions
componentDidMount() {
this._isMounted = true;
const { currentAccount, navigation } = this.props;
const username = currentAccount && currentAccount.name ? currentAccount.name : '';
let isReply;
let isEdit;
let post;
let _draft;
if (navigation.state && navigation.state.params) {
const navigationParams = navigation.state.params;
if (navigationParams.draft) {
_draft = navigationParams.draft;
this.setState({
draftId: _draft._id,
isDraft: true,
});
this._getStorageDraft(username, isReply, _draft);
}
if (navigationParams.community) {
this.setState({
community: navigationParams.community,
});
}
if (navigationParams.upload) {
const { upload } = navigationParams;
upload.forEach((el) => {
if (el.filePath && el.fileName) {
this.setState({ isUploading: true });
const _media = {
path: el.filePath,
mime: el.mimeType,
filename: el.fileName || `img_${Math.random()}.jpg`,
};
this._uploadImage(_media);
} else if (el.text) {
this.setState({
draftPost: {
title: '',
body: el.text,
tags: [],
},
});
}
});
}
if (navigationParams.post) {
({ post } = navigationParams);
this.setState({
post,
});
}
if (navigationParams.isReply) {
({ isReply } = navigationParams);
this.setState({
isReply,
});
}
if (navigationParams.isEdit) {
({ isEdit } = navigationParams);
this.setState({
isEdit,
draftPost: {
title: get(post, 'title', ''),
body: get(post, 'markdownBody', ''),
tags: get(post, 'json_metadata.tags', []),
},
});
}
if (navigationParams.action) {
this._handleRoutingAction(navigationParams.action);
}
} else {
this.setState({
autoFocusText: true,
});
}
if (!isEdit && !_draft) {
this._fetchDraftsForComparison(isReply);
}
}
componentWillUnmount() {
this._isMounted = false;
}
_getStorageDraft = async (username, isReply, paramDraft) => {
if (isReply) {
const draftReply = await AsyncStorage.getItem('temp-reply');
@ -75,18 +178,126 @@ class EditorContainer extends Component {
});
}
} else {
await getDraftPost(username).then((result) => {
if (result) {
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,
},
});
}
//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,
});
}
});
}
};
/**
* this fucntion is run if editor is access used 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 } = this.props;
const username = get(currentAccount, 'name', '');
//initilizes editor with reply or non remote id less draft
const _getStorageDraftGeneral = () => {
this._getStorageDraft(username, isReply);
};
//skip comparison if its a reply and run general function
if (isReply) {
_getStorageDraftGeneral();
return;
}
try {
//if not logged in use non remote draft
if (!isLoggedIn) {
_getStorageDraftGeneral();
return;
}
const drafts = await getDrafts(username);
const idLessDraft = await getDraftPost(username);
const loadRecentDraft = () => {
//if no draft available means local draft is recent
if (drafts.length == 0) {
_getStorageDraftGeneral();
return;
}
//sort darts based on timestamps
drafts.sort((d1, d2) =>
new Date(d1.modified).getTime() < new Date(d2.modified).getTime() ? 1 : -1,
);
const _draft = drafts[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 && new Date(_draft.modified).getTime() < idLessDraft.timestamp) {
_getStorageDraftGeneral();
return;
}
//initilize editor as draft
this.setState({
draftId: _draft._id,
isDraft: true,
});
this._getStorageDraft(username, isReply, _draft);
};
const leaveEmpty = () => {
console.log('Leaving editor empty');
};
if (drafts.length > 0 || (idLessDraft && idLessDraft.timestamp > 0)) {
Alert.alert(
intl.formatMessage({
id: 'editor.alert_init_title',
}),
intl.formatMessage({
id: 'editor.alert_init_body',
}),
[
{
text: intl.formatMessage({ id: 'editor.alert_btn_draft' }),
onPress: loadRecentDraft,
},
{ text: intl.formatMessage({ id: 'editor.alert_btn_new' }), onPress: leaveEmpty },
],
);
}
} catch (err) {
console.warn('Failed to compare drafts, load general', err);
_getStorageDraftGeneral();
}
};
_handleRoutingAction = (routingAction) => {
@ -224,17 +435,21 @@ class EditorContainer extends Component {
}
};
_saveDraftToDB = (fields) => {
_saveDraftToDB = async (fields) => {
const { isDraftSaved, draftId } = this.state;
const { currentAccount, dispatch, intl } = this.props;
try {
if (!isDraftSaved) {
const { currentAccount } = this.props;
const username = get(currentAccount, 'name', '');
let draftField;
if (this._isMounted) {
this.setState({
isDraftSaving: true,
});
}
if (fields) {
draftField = {
...fields,
@ -243,35 +458,78 @@ class EditorContainer extends Component {
};
}
//update draft is draftId is present
if (draftId && draftField) {
updateDraft({
await updateDraft({
...draftField,
draftId,
}).then(() => {
});
if (this._isMounted) {
this.setState({
isDraftSaved: true,
isDraftSaving: false,
});
});
} else if (draftField) {
addDraft(draftField).then((response) => {
}
}
//create new darft otherwise
else if (draftField) {
const response = await addDraft(draftField);
if (this._isMounted) {
this.setState({
isDraftSaved: true,
isDraftSaving: false,
draftId: response._id,
});
});
}
//clear local copy is darft save is successful
setDraftPost(
{
title: '',
body: '',
tags: '',
timestamp: 0,
},
username,
);
}
dispatch(
toastNotification(
intl.formatMessage({
id: 'editor.draft_save_success',
}),
),
);
//call fetch post to drafts screen
this._navigationBackFetchDrafts();
}
} catch (err) {
console.warn('Failed to save draft to DB: ', err);
if (this._isMounted) {
this.setState({
isDraftSaving: false,
isDraftSaved,
isDraftSaved: false,
});
}
dispatch(
toastNotification(
intl.formatMessage({
id: 'editor.draft_save_fail',
}),
),
);
}
};
_saveCurrentDraft = async (fields) => {
const { draftId, isReply, isEdit, isPostSending } = this.state;
if (!draftId && !isEdit) {
const { currentAccount } = this.props;
const username = currentAccount && currentAccount.name ? currentAccount.name : '';
@ -279,12 +537,20 @@ class EditorContainer extends Component {
...fields,
tags: fields.tags && fields.tags.length > 0 ? fields.tags.toString() : '',
};
if (!isPostSending) {
//save reply data
if (isReply && draftField.body !== null) {
await AsyncStorage.setItem('temp-reply', draftField.body);
} else {
setDraftPost(draftField, username);
//save existing draft data locally
} else if (draftId) {
setDraftPost(draftField, username, draftId);
}
//update editor data locally
else if (!isReply) {
setDraftPost(draftField, username);
}
}
};
@ -363,6 +629,7 @@ class EditorContainer extends Component {
title: '',
body: '',
tags: '',
timestamp: 0,
},
currentAccount.name,
);
@ -410,12 +677,7 @@ class EditorContainer extends Component {
const permlink = generateReplyPermlink(post.author);
const author = currentAccount.name;
const options = null;
/*makeOptions({
author: author,
permlink: permlink,
operationType: rewardType,
beneficiaries: beneficiaries,
});*/
const parentAuthor = post.author;
const parentPermlink = post.permlink;
const voteWeight = null;
@ -545,45 +807,68 @@ class EditorContainer extends Component {
}, 3000);
};
_handleOnBackPress = () => {
_navigationBackFetchDrafts = () => {
const { navigation } = this.props;
const { isDraft } = this.state;
if (isDraft) {
if (isDraft && navigation.state.params) {
navigation.state.params.fetchPost();
}
};
_handleSubmit = (form) => {
const { isReply, isEdit } = this.state;
const { intl } = this.props;
if (isReply && !isEdit) {
this._submitReply(form.fields);
} else if (isEdit) {
Alert.alert(
'Publishing edits',
'Are you sure?',
intl.formatMessage({
id: 'editor.alert_pub_edit_title',
}),
intl.formatMessage({
id: 'editor.alert_pub_body',
}),
[
{
text: 'No',
text: intl.formatMessage({
id: 'editor.alert_btn_no',
}),
onPress: () => console.log('Cancel Pressed'),
style: 'cancel',
},
{ text: 'Yes', onPress: () => this._submitEdit(form.fields) },
{
text: intl.formatMessage({
id: 'editor.alert_btn_yes',
}),
onPress: () => this._submitEdit(form.fields),
},
],
{ cancelable: false },
);
} else {
Alert.alert(
'Publishing new post',
'Are you sure?',
intl.formatMessage({
id: 'editor.alert_pub_new_title',
}),
intl.formatMessage({
id: 'editor.alert_pub_body',
}),
[
{
text: 'No',
text: intl.formatMessage({
id: 'editor.alert_btn_no',
}),
onPress: () => console.log('Cancel Pressed'),
style: 'cancel',
},
{ text: 'Yes', onPress: () => this._submitPost(form.fields) },
{
text: intl.formatMessage({
id: 'editor.alert_btn_yes',
}),
onPress: () => this._submitPost(form.fields),
},
],
{ cancelable: false },
);
@ -683,6 +968,7 @@ class EditorContainer extends Component {
title: '',
body: '',
tags: '',
timestamp: 0,
},
currentAccount.name,
);
@ -710,6 +996,7 @@ class EditorContainer extends Component {
title: '',
body: '',
tags: '',
timestamp: 0,
},
name,
);
@ -728,100 +1015,6 @@ class EditorContainer extends Component {
await AsyncStorage.setItem('temp-beneficiaries', JSON.stringify(value));
};
// Component Life Cycle Functions
componentDidMount() {
const { currentAccount, navigation } = this.props;
const username = currentAccount && currentAccount.name ? currentAccount.name : '';
let isReply;
let isEdit;
let post;
let _draft;
if (navigation.state && navigation.state.params) {
const navigationParams = navigation.state.params;
if (navigationParams.draft) {
_draft = navigationParams.draft;
const _tags = _draft.tags.includes(' ') ? _draft.tags.split(' ') : _draft.tags.split(',');
this.setState({
draftPost: {
title: _draft.title,
body: _draft.body,
tags: _tags,
},
draftId: _draft._id,
isDraft: true,
});
}
if (navigationParams.community) {
this.setState({
community: navigationParams.community,
});
}
if (navigationParams.upload) {
const { upload } = navigationParams;
upload.forEach((el) => {
if (el.filePath && el.fileName) {
this.setState({ isUploading: true });
const _media = {
path: el.filePath,
mime: el.mimeType,
filename: el.fileName || `img_${Math.random()}.jpg`,
};
this._uploadImage(_media);
} else if (el.text) {
this.setState({
draftPost: {
title: '',
body: el.text,
tags: [],
},
});
}
});
}
if (navigationParams.post) {
({ post } = navigationParams);
this.setState({
post,
});
}
if (navigationParams.isReply) {
({ isReply } = navigationParams);
this.setState({
isReply,
});
}
if (navigationParams.isEdit) {
({ isEdit } = navigationParams);
this.setState({
isEdit,
draftPost: {
title: get(post, 'title', ''),
body: get(post, 'markdownBody', ''),
tags: get(post, 'json_metadata.tags', []),
},
});
}
if (navigationParams.action) {
this._handleRoutingAction(navigationParams.action);
}
} else {
this.setState({
autoFocusText: true,
});
}
if (!isEdit && !_draft) {
this._getDraft(username, isReply);
}
}
render() {
const { isLoggedIn, isDarkTheme, navigation, currentAccount } = this.props;
const {
@ -850,7 +1043,7 @@ class EditorContainer extends Component {
handleBeneficiaries={this._handleBeneficiaries}
handleDatePickerChange={this._handleDatePickerChange}
handleFormChanged={this._handleFormChanged}
handleOnBackPress={this._handleOnBackPress}
handleOnBackPress={() => {}}
handleOnImagePicker={this._handleRoutingAction}
handleOnSubmit={this._handleSubmit}
initialEditor={this._initialEditor}

View File

@ -65,6 +65,10 @@ class EditorScreen extends Component {
}
}
componentWillUnmount() {
this._saveDraftToDB();
}
UNSAFE_componentWillReceiveProps = async (nextProps) => {
const { draftPost, isUploading, community } = this.props;
if (nextProps.draftPost && draftPost !== nextProps.draftPost) {
@ -127,10 +131,7 @@ class EditorScreen extends Component {
};
_handleOnSaveButtonPress = () => {
const { saveDraftToDB } = this.props;
const { fields } = this.state;
saveDraftToDB(fields);
this._saveDraftToDB();
};
_saveCurrentDraft = (fields) => {
@ -252,6 +253,16 @@ class EditorScreen extends Component {
});
};
_saveDraftToDB() {
const { saveDraftToDB } = this.props;
const { fields } = this.state;
//save draft only if any of field is valid
if (fields.body || fields.title) {
saveDraftToDB(fields);
}
}
render() {
const {
fields,

View File

@ -1583,24 +1583,24 @@ anymatch@^2.0.0:
micromatch "^3.1.4"
normalize-path "^2.1.1"
appcenter-analytics@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/appcenter-analytics/-/appcenter-analytics-3.1.0.tgz#ec51c79f43c58a6e6d6d5ce4c8bdb0ada3bda539"
integrity sha512-zGJRBe39IigyT127/alNzNeOpDYSyRo8RX2RYq2AtL01LnCreOy/fpzoF4yOVPMedFMiinEN+OOqR9e7WydAwg==
appcenter-analytics@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/appcenter-analytics/-/appcenter-analytics-4.1.0.tgz#eb3270a6894bcf3d14e1772c4ccf21d7fb88fa85"
integrity sha512-pEsz08wer7J3+zjNV78eSB0FY1bjMxmFHukV6PimAoMpIm8E+X7saGP0QQw2cQTUFiexMujHRvZfM259ksg6hg==
dependencies:
appcenter "3.1.0"
appcenter "4.1.0"
appcenter-crashes@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/appcenter-crashes/-/appcenter-crashes-3.1.0.tgz#6b0fb592bc5e69cbd83106b47f55c97bc33a3fa3"
integrity sha512-2dwy7aqhSKifWg5yZVPuZdNRiHMO/XTsaQSHvvHNDA47BJ2y/cwCsK516KNgoYvp97B3Pr3Xyim7ee/n/xxO4g==
appcenter-crashes@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/appcenter-crashes/-/appcenter-crashes-4.1.0.tgz#1d7a1a27c61f6a37d904f3d435de2f30ee0454e5"
integrity sha512-erl7387yAPRKa7d8L7KxNd82OAZ+EQwThpsu7tZLQYn3iuIT4P98dNFCnJKO6y64li4dn4B9rCF3s0PN7tPb1A==
dependencies:
appcenter "3.1.0"
appcenter "4.1.0"
appcenter-link-scripts@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/appcenter-link-scripts/-/appcenter-link-scripts-3.1.0.tgz#5cc9d0303f9f16b7170d56a403fa3e69379cfd36"
integrity sha512-0ECoQLjfIJhAO8EkvRQ4fO5gZELhzwD3eRhsCD4d27k7TDPXYt32/p5ylHXUWotR7pduTE1rZ8gtETTr4oZf9g==
appcenter-link-scripts@4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/appcenter-link-scripts/-/appcenter-link-scripts-4.1.0.tgz#ff9d337fb7841c2cbf86cd5f34bd7310e945d0f5"
integrity sha512-sTd8H4H9wNwBkUKHasvDGNBln34+dHEFlaT/P6O0gvs6TWU3JxlY8uXpB/IqAi2lSWPs3TMgQ7NGXTjvC6oZig==
dependencies:
debug "4.1.1"
glob "5.0.15"
@ -1610,12 +1610,12 @@ appcenter-link-scripts@3.1.0:
which "1.2.11"
xcode "2.0.0"
appcenter@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/appcenter/-/appcenter-3.1.0.tgz#55fb01bfedc92e732e52ab97c8b344e062ba61a7"
integrity sha512-SvEdrqeEqK5BJizdrRmWdp/NFwwb3FJ08U5LQkRdqPoFWXQZ7d+AC2cVnemORXsiYPcpwK9xqiNxyofzEzddFA==
appcenter@4.1.0, appcenter@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/appcenter/-/appcenter-4.1.0.tgz#d4c9b43adcaac32a328dc8e9f9f0607e0a7d5691"
integrity sha512-JUwb/4sMw68CN2AlJEp1G5iVl/Rzv8fg8t9CzeRaEcnOku42h6+vlYbYe3P5YW18kAIA/hCH0uoDaa0kc9qfDA==
dependencies:
appcenter-link-scripts "3.1.0"
appcenter-link-scripts "4.1.0"
aproba@^1.0.3:
version "1.2.0"