Add the ability to paste images into the composer (#56)
parent
15589d216f
commit
570b76a71d
|
@ -244,6 +244,9 @@ PODS:
|
||||||
- React-Core
|
- React-Core
|
||||||
- react-native-pager-view (6.1.2):
|
- react-native-pager-view (6.1.2):
|
||||||
- React-Core
|
- React-Core
|
||||||
|
- react-native-paste-input (0.6.0):
|
||||||
|
- React-Core
|
||||||
|
- Swime (= 3.0.6)
|
||||||
- react-native-safe-area-context (4.4.1):
|
- react-native-safe-area-context (4.4.1):
|
||||||
- RCT-Folly
|
- RCT-Folly
|
||||||
- RCTRequired
|
- RCTRequired
|
||||||
|
@ -390,6 +393,7 @@ PODS:
|
||||||
- React-RCTImage
|
- React-RCTImage
|
||||||
- RNSVG (12.5.0):
|
- RNSVG (12.5.0):
|
||||||
- React-Core
|
- React-Core
|
||||||
|
- Swime (3.0.6)
|
||||||
- TOCropViewController (2.6.1)
|
- TOCropViewController (2.6.1)
|
||||||
- Yoga (1.14.0)
|
- Yoga (1.14.0)
|
||||||
|
|
||||||
|
@ -421,6 +425,7 @@ DEPENDENCIES:
|
||||||
- "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)"
|
- "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)"
|
||||||
- "react-native-image-resizer (from `../node_modules/@bam.tech/react-native-image-resizer`)"
|
- "react-native-image-resizer (from `../node_modules/@bam.tech/react-native-image-resizer`)"
|
||||||
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
|
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
|
||||||
|
- "react-native-paste-input (from `../node_modules/@mattermost/react-native-paste-input`)"
|
||||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||||
- react-native-splash-screen (from `../node_modules/react-native-splash-screen`)
|
- react-native-splash-screen (from `../node_modules/react-native-splash-screen`)
|
||||||
- react-native-version-number (from `../node_modules/react-native-version-number`)
|
- react-native-version-number (from `../node_modules/react-native-version-number`)
|
||||||
|
@ -454,6 +459,7 @@ SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
- fmt
|
- fmt
|
||||||
- libevent
|
- libevent
|
||||||
|
- Swime
|
||||||
- TOCropViewController
|
- TOCropViewController
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
|
@ -507,6 +513,8 @@ EXTERNAL SOURCES:
|
||||||
:path: "../node_modules/@bam.tech/react-native-image-resizer"
|
:path: "../node_modules/@bam.tech/react-native-image-resizer"
|
||||||
react-native-pager-view:
|
react-native-pager-view:
|
||||||
:path: "../node_modules/react-native-pager-view"
|
:path: "../node_modules/react-native-pager-view"
|
||||||
|
react-native-paste-input:
|
||||||
|
:path: "../node_modules/@mattermost/react-native-paste-input"
|
||||||
react-native-safe-area-context:
|
react-native-safe-area-context:
|
||||||
:path: "../node_modules/react-native-safe-area-context"
|
:path: "../node_modules/react-native-safe-area-context"
|
||||||
react-native-splash-screen:
|
react-native-splash-screen:
|
||||||
|
@ -592,6 +600,7 @@ SPEC CHECKSUMS:
|
||||||
react-native-cameraroll: 0ff04cc4e0ff5f19a94ff4313e5c8bc4503cd86d
|
react-native-cameraroll: 0ff04cc4e0ff5f19a94ff4313e5c8bc4503cd86d
|
||||||
react-native-image-resizer: 794abf75ec13ed1f0dbb1f134e27504ea65e9e66
|
react-native-image-resizer: 794abf75ec13ed1f0dbb1f134e27504ea65e9e66
|
||||||
react-native-pager-view: 54bed894cecebe28cede54c01038d9d1e122de43
|
react-native-pager-view: 54bed894cecebe28cede54c01038d9d1e122de43
|
||||||
|
react-native-paste-input: 5182843692fd2ec72be50f241a38a49796e225d7
|
||||||
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
|
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
|
||||||
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
||||||
react-native-version-number: b415bbec6a13f2df62bf978e85bc0d699462f37f
|
react-native-version-number: b415bbec6a13f2df62bf978e85bc0d699462f37f
|
||||||
|
@ -619,6 +628,7 @@ SPEC CHECKSUMS:
|
||||||
RNReanimated: d8d9d3d3801bda5e35e85cdffc871577d044dc2e
|
RNReanimated: d8d9d3d3801bda5e35e85cdffc871577d044dc2e
|
||||||
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
|
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
|
||||||
RNSVG: 6adc5c52d2488a476248413064b7f2832e639057
|
RNSVG: 6adc5c52d2488a476248413064b7f2832e639057
|
||||||
|
Swime: d7b2c277503b6cea317774aedc2dce05613f8b0b
|
||||||
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
|
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
|
||||||
Yoga: c618b544ff8bd8865cdca602f00cbcdb92fd6d31
|
Yoga: c618b544ff8bd8865cdca602f00cbcdb92fd6d31
|
||||||
|
|
||||||
|
|
10
package.json
10
package.json
|
@ -24,6 +24,7 @@
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||||
"@fortawesome/react-native-fontawesome": "^0.3.0",
|
"@fortawesome/react-native-fontawesome": "^0.3.0",
|
||||||
"@gorhom/bottom-sheet": "^4",
|
"@gorhom/bottom-sheet": "^4",
|
||||||
|
"@mattermost/react-native-paste-input": "^0.6.0",
|
||||||
"@react-native-async-storage/async-storage": "^1.17.6",
|
"@react-native-async-storage/async-storage": "^1.17.6",
|
||||||
"@react-native-camera-roll/camera-roll": "^5.1.0",
|
"@react-native-camera-roll/camera-roll": "^5.1.0",
|
||||||
"@react-native-clipboard/clipboard": "^1.10.0",
|
"@react-native-clipboard/clipboard": "^1.10.0",
|
||||||
|
@ -115,7 +116,9 @@
|
||||||
"transformIgnorePatterns": [
|
"transformIgnorePatterns": [
|
||||||
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|rollbar-react-native|@fortawesome|@react-native|@react-navigation)"
|
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|rollbar-react-native|@fortawesome|@react-native|@react-navigation)"
|
||||||
],
|
],
|
||||||
"modulePathIgnorePatterns": ["__tests__\/.*\/__mocks__"],
|
"modulePathIgnorePatterns": [
|
||||||
|
"__tests__/.*/__mocks__"
|
||||||
|
],
|
||||||
"coveragePathIgnorePatterns": [
|
"coveragePathIgnorePatterns": [
|
||||||
"<rootDir>/node_modules/",
|
"<rootDir>/node_modules/",
|
||||||
"<rootDir>/src/platform",
|
"<rootDir>/src/platform",
|
||||||
|
@ -124,7 +127,10 @@
|
||||||
"<rootDir>/src/state/lib",
|
"<rootDir>/src/state/lib",
|
||||||
"<rootDir>/__tests__/test-utils.js"
|
"<rootDir>/__tests__/test-utils.js"
|
||||||
],
|
],
|
||||||
"reporters": [ "default", "jest-junit" ]
|
"reporters": [
|
||||||
|
"default",
|
||||||
|
"jest-junit"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|
|
@ -7,11 +7,14 @@ import {
|
||||||
SafeAreaView,
|
SafeAreaView,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TextInput,
|
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
TouchableWithoutFeedback,
|
TouchableWithoutFeedback,
|
||||||
View,
|
View,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
|
import PasteInput, {
|
||||||
|
PastedFile,
|
||||||
|
PasteInputRef,
|
||||||
|
} from '@mattermost/react-native-paste-input'
|
||||||
import LinearGradient from 'react-native-linear-gradient'
|
import LinearGradient from 'react-native-linear-gradient'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {UserAutocompleteViewModel} from '../../../state/models/user-autocomplete-view'
|
import {UserAutocompleteViewModel} from '../../../state/models/user-autocomplete-view'
|
||||||
|
@ -33,7 +36,7 @@ import {detectLinkables, extractEntities} from '../../../lib/strings'
|
||||||
import {getLinkMeta} from '../../../lib/link-meta'
|
import {getLinkMeta} from '../../../lib/link-meta'
|
||||||
import {downloadAndResize} from '../../../lib/images'
|
import {downloadAndResize} from '../../../lib/images'
|
||||||
import {UserLocalPhotosModel} from '../../../state/models/user-local-photos'
|
import {UserLocalPhotosModel} from '../../../state/models/user-local-photos'
|
||||||
import {PhotoCarouselPicker} from './PhotoCarouselPicker'
|
import {PhotoCarouselPicker, cropPhoto} from './PhotoCarouselPicker'
|
||||||
import {SelectedPhoto} from './SelectedPhoto'
|
import {SelectedPhoto} from './SelectedPhoto'
|
||||||
import {usePalette} from '../../lib/hooks/usePalette'
|
import {usePalette} from '../../lib/hooks/usePalette'
|
||||||
|
|
||||||
|
@ -54,7 +57,7 @@ export const ComposePost = observer(function ComposePost({
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const textInput = useRef<TextInput>(null)
|
const textInput = useRef<PasteInputRef>(null)
|
||||||
const [isProcessing, setIsProcessing] = useState(false)
|
const [isProcessing, setIsProcessing] = useState(false)
|
||||||
const [processingState, setProcessingState] = useState('')
|
const [processingState, setProcessingState] = useState('')
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
|
@ -78,6 +81,16 @@ export const ComposePost = observer(function ComposePost({
|
||||||
[store],
|
[store],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// HACK
|
||||||
|
// there's a bug with @mattermost/react-native-paste-input where if the input
|
||||||
|
// is focused during unmount, an exception will throw (seems that a blur method isnt implemented)
|
||||||
|
// manually blurring before closing gets around that
|
||||||
|
// -prf
|
||||||
|
const hackfixOnClose = () => {
|
||||||
|
textInput.current?.blur()
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
|
||||||
// initial setup
|
// initial setup
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
autocompleteView.setup()
|
autocompleteView.setup()
|
||||||
|
@ -134,7 +147,8 @@ export const ComposePost = observer(function ComposePost({
|
||||||
isLoading: false, // done
|
isLoading: false, // done
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [extLink])
|
return cleanup
|
||||||
|
}, [store, extLink])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// HACK
|
// HACK
|
||||||
|
@ -196,9 +210,21 @@ export const ComposePost = observer(function ComposePost({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const onPressCancel = () => {
|
const onPaste = async (err: string | undefined, files: PastedFile[]) => {
|
||||||
onClose()
|
if (err) {
|
||||||
|
return setError(err)
|
||||||
}
|
}
|
||||||
|
if (selectedPhotos.length >= 4) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const imgFile = files.find(file => /\.(jpe?g|png)$/.test(file.fileName))
|
||||||
|
if (!imgFile) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const finalImgPath = await cropPhoto(imgFile.uri)
|
||||||
|
onSelectPhotos([...selectedPhotos, finalImgPath])
|
||||||
|
}
|
||||||
|
const onPressCancel = () => hackfixOnClose()
|
||||||
const onPressPublish = async () => {
|
const onPressPublish = async () => {
|
||||||
if (isProcessing) {
|
if (isProcessing) {
|
||||||
return
|
return
|
||||||
|
@ -229,7 +255,7 @@ export const ComposePost = observer(function ComposePost({
|
||||||
}
|
}
|
||||||
store.me.mainFeed.loadLatest()
|
store.me.mainFeed.loadLatest()
|
||||||
onPost?.()
|
onPost?.()
|
||||||
onClose()
|
hackfixOnClose()
|
||||||
Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`)
|
Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`)
|
||||||
}
|
}
|
||||||
const onSelectAutocompleteItem = (item: string) => {
|
const onSelectAutocompleteItem = (item: string) => {
|
||||||
|
@ -254,7 +280,11 @@ export const ComposePost = observer(function ComposePost({
|
||||||
let i = 0
|
let i = 0
|
||||||
return detectLinkables(text).map(v => {
|
return detectLinkables(text).map(v => {
|
||||||
if (typeof v === 'string') {
|
if (typeof v === 'string') {
|
||||||
return v
|
return (
|
||||||
|
<Text key={i++} style={styles.textInputFormatting}>
|
||||||
|
{v}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Text key={i++} style={[pal.link, styles.textInputFormatting]}>
|
<Text key={i++} style={[pal.link, styles.textInputFormatting]}>
|
||||||
|
@ -263,7 +293,7 @@ export const ComposePost = observer(function ComposePost({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [text])
|
}, [text, pal.link])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
|
@ -354,12 +384,13 @@ export const ComposePost = observer(function ComposePost({
|
||||||
avatar={store.me.avatar}
|
avatar={store.me.avatar}
|
||||||
size={50}
|
size={50}
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<PasteInput
|
||||||
testID="composerTextInput"
|
testID="composerTextInput"
|
||||||
ref={textInput}
|
ref={textInput}
|
||||||
multiline
|
multiline
|
||||||
scrollEnabled
|
scrollEnabled
|
||||||
onChangeText={(text: string) => onChangeText(text)}
|
onChangeText={(text: string) => onChangeText(text)}
|
||||||
|
onPaste={onPaste}
|
||||||
placeholder={selectTextInputPlaceholder}
|
placeholder={selectTextInputPlaceholder}
|
||||||
placeholderTextColor={pal.colors.textLight}
|
placeholderTextColor={pal.colors.textLight}
|
||||||
style={[
|
style={[
|
||||||
|
@ -368,7 +399,7 @@ export const ComposePost = observer(function ComposePost({
|
||||||
styles.textInputFormatting,
|
styles.textInputFormatting,
|
||||||
]}>
|
]}>
|
||||||
{textDecorated}
|
{textDecorated}
|
||||||
</TextInput>
|
</PasteInput>
|
||||||
</View>
|
</View>
|
||||||
<SelectedPhoto
|
<SelectedPhoto
|
||||||
selectedPhotos={selectedPhotos}
|
selectedPhotos={selectedPhotos}
|
||||||
|
|
|
@ -26,6 +26,28 @@ const IMAGE_PARAMS = {
|
||||||
compressImageQuality: 1.0,
|
compressImageQuality: 1.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function cropPhoto(
|
||||||
|
path: string,
|
||||||
|
imgWidth = MAX_WIDTH,
|
||||||
|
imgHeight = MAX_HEIGHT,
|
||||||
|
) {
|
||||||
|
// choose target dimensions based on the original
|
||||||
|
// this causes the photo cropper to start with the full image "selected"
|
||||||
|
const {width, height} = scaleDownDimensions(
|
||||||
|
{width: imgWidth, height: imgHeight},
|
||||||
|
{width: MAX_WIDTH, height: MAX_HEIGHT},
|
||||||
|
)
|
||||||
|
const cropperRes = await openCropper({
|
||||||
|
mediaType: 'photo',
|
||||||
|
path,
|
||||||
|
...IMAGE_PARAMS,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
})
|
||||||
|
const img = await compressIfNeeded(cropperRes, MAX_SIZE)
|
||||||
|
return img.path
|
||||||
|
}
|
||||||
|
|
||||||
export const PhotoCarouselPicker = ({
|
export const PhotoCarouselPicker = ({
|
||||||
selectedPhotos,
|
selectedPhotos,
|
||||||
onSelectPhotos,
|
onSelectPhotos,
|
||||||
|
@ -55,21 +77,12 @@ export const PhotoCarouselPicker = ({
|
||||||
const handleSelectPhoto = useCallback(
|
const handleSelectPhoto = useCallback(
|
||||||
async (item: PhotoIdentifier) => {
|
async (item: PhotoIdentifier) => {
|
||||||
try {
|
try {
|
||||||
// choose target dimensions based on the original
|
const imgPath = await cropPhoto(
|
||||||
// this causes the photo cropper to start with the full image "selected"
|
item.node.image.uri,
|
||||||
const {width, height} = scaleDownDimensions(
|
item.node.image.width,
|
||||||
{width: item.node.image.width, height: item.node.image.height},
|
item.node.image.height,
|
||||||
{width: MAX_WIDTH, height: MAX_HEIGHT},
|
|
||||||
)
|
)
|
||||||
const cropperRes = await openCropper({
|
onSelectPhotos([...selectedPhotos, imgPath])
|
||||||
mediaType: 'photo',
|
|
||||||
path: item.node.image.uri,
|
|
||||||
...IMAGE_PARAMS,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
})
|
|
||||||
const img = await compressIfNeeded(cropperRes, MAX_SIZE)
|
|
||||||
onSelectPhotos([...selectedPhotos, img.path])
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
// ignore
|
// ignore
|
||||||
store.log.warn('Error selecting photo', err)
|
store.log.warn('Error selecting photo', err)
|
||||||
|
|
21
yarn.lock
21
yarn.lock
|
@ -1907,6 +1907,13 @@
|
||||||
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
|
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
|
||||||
integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
|
integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
|
||||||
|
|
||||||
|
"@mattermost/react-native-paste-input@^0.6.0":
|
||||||
|
version "0.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@mattermost/react-native-paste-input/-/react-native-paste-input-0.6.0.tgz#bc920c8f1b442266a6bc58f9122df137b73bc9fa"
|
||||||
|
integrity sha512-Hy4w8RaiiXl2AKcLXT0FjJJsh4FXtLiWCxfh6zaBtCkx7jsr4d9xwJ/zqrnjv0jkG7XbRUCp40dgNBpWYZ1pyQ==
|
||||||
|
dependencies:
|
||||||
|
semver "7.3.8"
|
||||||
|
|
||||||
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
|
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
|
||||||
version "5.1.1-v1"
|
version "5.1.1-v1"
|
||||||
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
|
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
|
||||||
|
@ -11039,6 +11046,13 @@ selfsigned@^2.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
node-forge "^1"
|
node-forge "^1"
|
||||||
|
|
||||||
|
semver@7.3.8, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8:
|
||||||
|
version "7.3.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
|
||||||
|
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^6.0.0"
|
||||||
|
|
||||||
semver@^5.5.0, semver@^5.6.0:
|
semver@^5.5.0, semver@^5.6.0:
|
||||||
version "5.7.1"
|
version "5.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||||
|
@ -11049,13 +11063,6 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
|
||||||
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
|
||||||
|
|
||||||
semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8:
|
|
||||||
version "7.3.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
|
|
||||||
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
|
|
||||||
dependencies:
|
|
||||||
lru-cache "^6.0.0"
|
|
||||||
|
|
||||||
send@0.18.0:
|
send@0.18.0:
|
||||||
version "0.18.0"
|
version "0.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
|
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
|
||||||
|
|
Loading…
Reference in New Issue