Merge remote-tracking branch 'origin/develop' into disjointed-popovers

* origin/develop: (25 commits)
  force panel headers to be square on mobile (for now?)
  fix gap between panel heading and timeline menu
  Fix Open Chat button
  fix?
  fix
  Revert "Merge branch 'revert-a88abc7e' into 'develop'"
  Revert "Merge branch 'from/develop/tusooa/lang-opts' into 'develop'"
  Fix registration error
  stick chat scroll to bottom to help with OSK resizing the viewport
  Pass file name of cropped avatar to form data
  Add English translation for filtering end-of-poll notifications
  Add settings for filtering end-of-poll notifications
  Add English translations for poll-end notifications
  Show poll-end notifications
  Fix virtual scrolling when the user has a lot of pinned statuses
  Update dependency @vuelidate/core to v2.0.0-alpha.41
  Make lint happy
  Make lint happy
  Add English translation for language options
  Add email language option to registration form
  ...
This commit is contained in:
Henry Jameson 2022-06-08 02:13:47 +03:00
commit 1154a6514b
26 changed files with 224 additions and 77 deletions

View File

@ -23,7 +23,7 @@
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/vue-fontawesome": "3.0.0-5",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
"@vuelidate/core": "2.0.0-alpha.35",
"@vuelidate/core": "2.0.0-alpha.41",
"@vuelidate/validators": "2.0.0-alpha.27",
"body-scroll-lock": "2.7.1",
"chromatism": "3.0.0",
@ -31,6 +31,7 @@
"cropperjs": "1.5.12",
"diff": "3.5.0",
"escape-html": "1.0.3",
"js-cookie": "^3.0.1",
"localforage": "1.10.0",
"parse-link-header": "1.0.1",
"phoenix": "1.6.2",
@ -106,7 +107,7 @@
"sass": "1.20.1",
"sass-loader": "7.2.0",
"selenium-server": "2.53.1",
"semver": "5.6.0",
"semver": "5.7.1",
"serviceworker-webpack-plugin": "1.0.1",
"shelljs": "0.8.5",
"sinon": "2.4.1",
@ -120,7 +121,7 @@
"webpack": "4.46.0",
"webpack-dev-middleware": "3.7.3",
"webpack-hot-middleware": "2.24.3",
"webpack-merge": "0.14.1"
"webpack-merge": "0.20.0"
},
"engines": {
"node": ">= 4.0.0",

View File

@ -301,6 +301,15 @@ nav {
margin-bottom: 0;
}
.panel-heading,
.panel-heading::after,
.panel-heading::before,
.panel,
.panel::after {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.underlay,
#sidebar,
#notifs-column {

View File

@ -40,7 +40,7 @@ const AccountActions = {
openChat () {
this.$router.push({
name: 'chat',
params: { recipient_id: this.user.id }
params: { username: this.$store.state.users.currentUser.screen_name, recipient_id: this.user.id }
})
}
},

View File

@ -43,6 +43,7 @@ const Chat = {
},
created () {
this.startFetching()
window.addEventListener('resize', this.handleResize)
},
mounted () {
window.addEventListener('scroll', this.handleScroll)
@ -132,7 +133,7 @@ const Chat = {
}
})
},
// Preserves the scroll position when OSK appears or the posting form changes its height.
// "Sticks" scroll to bottom instead of top, helps with OSK resizing the viewport
handleResize (opts = {}) {
const { expand = false, delayed = false } = opts
@ -144,15 +145,14 @@ const Chat = {
}
this.$nextTick(() => {
const { scrollHeight = undefined } = this.lastScrollPosition
this.lastScrollPosition = getScrollPosition()
const diff = this.lastScrollPosition.scrollHeight - scrollHeight
if (diff > 0 || (!this.bottomedOut() && expand)) {
const { offsetHeight = undefined } = getScrollPosition()
const diff = this.lastScrollPosition.offsetHeight - offsetHeight
if (diff !== 0 || (!this.bottomedOut() && expand)) {
this.$nextTick(() => {
window.scrollTo({ top: window.scrollY + diff })
})
}
this.lastScrollPosition = getScrollPosition()
})
},
scrollDown (options = {}) {

View File

@ -1,12 +1,12 @@
<template>
<div>
<label for="interface-language-switcher">
{{ $t('settings.interfaceLanguage') }}
{{ promptText }}
</label>
{{ ' ' }}
<Select
id="interface-language-switcher"
v-model="language"
v-model="controlledLanguage"
>
<option
v-for="lang in languages"
@ -20,39 +20,43 @@
</template>
<script>
import languagesObject from '../../i18n/messages'
import localeService from '../../services/locale/locale.service.js'
import ISO6391 from 'iso-639-1'
import _ from 'lodash'
import Select from '../select/select.vue'
export default {
components: {
Select
},
props: {
promptText: {
type: String,
required: true
},
language: {
type: String,
required: true
},
setLanguage: {
type: Function,
required: true
}
},
computed: {
languages () {
return _.map(languagesObject.languages, (code) => ({ code: code, name: this.getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name))
return localeService.languages
},
language: {
get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
controlledLanguage: {
get: function () { return this.language },
set: function (val) {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
this.setLanguage(val)
}
}
},
methods: {
getLanguageName (code) {
const specialLanguageNames = {
'ja_easy': 'やさしいにほんご',
'zh': '简体中文',
'zh_Hant': '繁體中文'
}
const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code)
const browserLocale = localeService.internalToBrowserLocale(code)
return languageName.charAt(0).toLocaleUpperCase(browserLocale) + languageName.slice(1)
return localeService.getLanguageName(code)
}
}
}

View File

@ -120,6 +120,14 @@
</i18n-t>
</small>
</span>
<span v-if="notification.type === 'poll'">
<FAIcon
class="type-icon"
icon="poll-h"
/>
{{ ' ' }}
<small>{{ $t('notifications.poll_ended') }}</small>
</span>
</div>
<div
v-if="isStatusNotification"

View File

@ -61,6 +61,15 @@
:class="{ 'menu-checkbox-checked': filters.moves }"
/>{{ $t('settings.notification_visibility_moves') }}
</button>
<button
class="button-default dropdown-item"
@click="toggleNotificationFilter('polls')"
>
<span
class="menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.polls }"
/>{{ $t('settings.notification_visibility_polls') }}
</button>
</div>
</template>
<template v-slot:trigger>

View File

@ -30,7 +30,7 @@
v-for="notification in notificationsToDisplay"
:key="notification.id"
class="notification"
:class="{&quot;unseen&quot;: !minimalMode && !notification.seen}"
:class="{unseen: !minimalMode && !notification.seen}"
>
<div class="notification-overlay" />
<notification :notification="notification" />

View File

@ -1,6 +1,8 @@
import useVuelidate from '@vuelidate/core'
import { required, requiredIf, sameAs } from '@vuelidate/validators'
import { mapActions, mapState } from 'vuex'
import InterfaceLanguageSwitcher from '../interface_language_switcher/interface_language_switcher.vue'
import localeService from '../../services/locale/locale.service.js'
const registration = {
setup () { return { v$: useVuelidate() } },
@ -11,10 +13,14 @@ const registration = {
username: '',
password: '',
confirm: '',
reason: ''
reason: '',
language: ''
},
captcha: {}
}),
components: {
InterfaceLanguageSwitcher
},
validations () {
return {
user: {
@ -26,7 +32,8 @@ const registration = {
required,
sameAs: sameAs(this.user.password)
},
reason: { required: requiredIf(() => this.accountApprovalRequired) }
reason: { required: requiredIf(() => this.accountApprovalRequired) },
language: {}
}
}
},
@ -64,6 +71,9 @@ const registration = {
this.user.captcha_solution = this.captcha.solution
this.user.captcha_token = this.captcha.token
this.user.captcha_answer_data = this.captcha.answer_data
if (this.user.language) {
this.user.language = localeService.internalToBackendLocale(this.user.language)
}
this.v$.$touch()

View File

@ -162,6 +162,18 @@
</ul>
</div>
<div
class="form-group"
:class="{ 'form-group--error': v$.user.language.$error }"
>
<interface-language-switcher
for="email-language"
:prompt-text="$t('registration.email_language')"
:language="v$.user.language.$model"
:set-language="val => v$.user.language.$model = val"
/>
</div>
<div
v-if="accountApprovalRequired"
class="form-group"

View File

@ -77,6 +77,12 @@ const GeneralTab = {
!this.$store.state.users.currentUser.background_image
},
instanceShoutboxPresent () { return this.$store.state.instance.shoutAvailable },
language: {
get: function () { return this.$store.getters.mergedConfig.interfaceLanguage },
set: function (val) {
this.$store.dispatch('setOption', { name: 'interfaceLanguage', value: val })
}
},
...SharedComputedObject()
},
methods: {

View File

@ -4,7 +4,11 @@
<h2>{{ $t('settings.interface') }}</h2>
<ul class="setting-list">
<li>
<interface-language-switcher />
<interface-language-switcher
:prompt-text="$t('settings.interfaceLanguage')"
:language="language"
:set-language="val => language = val"
/>
</li>
<li v-if="instanceSpecificPanelPresent">
<BooleanSetting path="hideISP">

View File

@ -41,6 +41,11 @@
{{ $t('settings.notification_visibility_emoji_reactions') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="notificationVisibility.polls">
{{ $t('settings.notification_visibility_polls') }}
</BooleanSetting>
</li>
</ul>
</li>
</ul>

View File

@ -8,8 +8,10 @@ import EmojiInput from 'src/components/emoji_input/emoji_input.vue'
import suggestor from 'src/components/emoji_input/suggestor.js'
import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
import BooleanSetting from '../helpers/boolean_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import localeService from 'src/services/locale/locale.service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@ -40,7 +42,8 @@ const ProfileTab = {
banner: null,
bannerPreview: null,
background: null,
backgroundPreview: null
backgroundPreview: null,
emailLanguage: this.$store.state.users.currentUser.language || ''
}
},
components: {
@ -50,7 +53,8 @@ const ProfileTab = {
Autosuggest,
ProgressButton,
Checkbox,
BooleanSetting
BooleanSetting,
InterfaceLanguageSwitcher
},
computed: {
user () {
@ -111,9 +115,7 @@ const ProfileTab = {
},
methods: {
updateProfile () {
this.$store.state.api.backendInteractor
.updateProfile({
params: {
const params = {
note: this.newBio,
locked: this.newLocked,
// Backend notation.
@ -123,7 +125,15 @@ const ProfileTab = {
bot: this.bot,
show_role: this.showRole
/* eslint-enable camelcase */
} }).then((user) => {
}
if (this.emailLanguage) {
params.language = localeService.internalToBackendLocale(this.emailLanguage)
}
this.$store.state.api.backendInteractor
.updateProfile({ params })
.then((user) => {
this.newFields.splice(user.fields.length)
merge(this.newFields, user.fields)
this.$store.commit('addNewUsers', [user])
@ -193,8 +203,8 @@ const ProfileTab = {
submitAvatar (cropper, file) {
const that = this
return new Promise((resolve, reject) => {
function updateAvatar (avatar) {
that.$store.state.api.backendInteractor.updateProfileImages({ avatar })
function updateAvatar (avatar, avatarName) {
that.$store.state.api.backendInteractor.updateProfileImages({ avatar, avatarName })
.then((user) => {
that.$store.commit('addNewUsers', [user])
that.$store.commit('setCurrentUser', user)
@ -207,9 +217,9 @@ const ProfileTab = {
}
if (cropper) {
cropper.getCroppedCanvas().toBlob(updateAvatar, file.type)
cropper.getCroppedCanvas().toBlob((data) => updateAvatar(data, file.name), file.type)
} else {
updateAvatar(file)
updateAvatar(file, file.name)
}
})
},

View File

@ -89,6 +89,13 @@
{{ $t('settings.bot') }}
</Checkbox>
</p>
<p>
<interface-language-switcher
:prompt-text="$t('settings.email_language')"
:language="emailLanguage"
:set-language="val => emailLanguage = val"
/>
</p>
<button
:disabled="newName && newName.length === 0"
class="btn button-default"

View File

@ -33,7 +33,7 @@
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
height: 1.4;
height: 1.4em;
}
}

View File

@ -77,8 +77,9 @@ const Timeline = {
statusesToDisplay () {
const amount = this.timeline.visibleStatuses.length
const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80))
const min = Math.max(0, this.virtualScrollIndex - statusesPerSide)
const max = Math.min(amount, this.virtualScrollIndex + statusesPerSide)
const nonPinnedIndex = this.virtualScrollIndex - this.filteredPinnedStatusIds.length
const min = Math.max(0, nonPinnedIndex - statusesPerSide)
const max = Math.min(amount, nonPinnedIndex + statusesPerSide)
return this.timeline.visibleStatuses.slice(min, max).map(_ => _.id)
},
virtualScrollingEnabled () {

View File

@ -93,7 +93,6 @@
<style lang="scss">
.TimelineQuickSettings {
align-self: stretch;
> button {
line-height: 100%;

View File

@ -43,6 +43,10 @@
min-width: 0;
width: 24rem;
.popover-trigger-button {
vertical-align: bottom;
}
.timeline-menu-popover-wrap {
overflow: hidden;
// Match panel heading padding to line up menu with bottom of heading

View File

@ -160,7 +160,8 @@
"repeated_you": "repeated your status",
"no_more_notifications": "No more notifications",
"migrated_to": "migrated to",
"reacted_with": "reacted with {0}"
"reacted_with": "reacted with {0}",
"poll_ended": "poll has ended"
},
"polls": {
"add_poll": "Add poll",
@ -254,7 +255,8 @@
"password_required": "cannot be left blank",
"password_confirmation_required": "cannot be left blank",
"password_confirmation_match": "should be the same as password"
}
},
"email_language": "In which language do you want to receive emails from the server?"
},
"remote_user_resolver": {
"remote_user_resolver": "Remote user resolver",
@ -303,6 +305,7 @@
"avatarRadius": "Avatars",
"background": "Background",
"bio": "Bio",
"email_language": "Language for receiving emails from the server",
"block_export": "Block export",
"block_export_button": "Export your blocks to a csv file",
"block_import": "Block import",
@ -427,6 +430,7 @@
"notification_visibility_repeats": "Repeats",
"notification_visibility_moves": "User Migrates",
"notification_visibility_emoji_reactions": "Reactions",
"notification_visibility_polls": "Ends of polls you voted in",
"no_rich_text_description": "Strip rich text formatting from all posts",
"no_blocks": "No blocks",
"no_mutes": "No mutes",

View File

@ -1,5 +1,9 @@
import Cookies from 'js-cookie'
import { setPreset, applyTheme } from '../services/style_setter/style_setter.js'
import messages from '../i18n/messages'
import localeService from '../services/locale/locale.service.js'
const BACKEND_LANGUAGE_COOKIE_NAME = 'userLanguage'
const browserLocale = (window.navigator.language || 'en').split('-')[0]
@ -55,7 +59,8 @@ export const defaultState = {
moves: true,
emojiReactions: true,
followRequest: true,
chatMention: true
chatMention: true,
polls: true
},
webPushNotifications: false,
muteWords: [],
@ -165,6 +170,7 @@ const config = {
break
case 'interfaceLanguage':
messages.setLanguage(this.getters.i18n, value)
Cookies.set(BACKEND_LANGUAGE_COOKIE_NAME, localeService.internalToBackendLocale(value))
break
case 'thirdColumnMode':
dispatch('setLayoutWidth', undefined)

View File

@ -55,7 +55,10 @@ export const settingsMap = {
get: 'pleroma.allow_following_move',
set: 'allow_following_move'
},
'discoverable': 'source.discoverable',
'discoverable': {
get: 'source.pleroma.discoverable',
set: 'discoverable'
},
'hideFavorites': {
get: 'pleroma.hide_favorites',
set: 'hide_favorites'

View File

@ -151,9 +151,15 @@ const updateNotificationSettings = ({ credentials, settings }) => {
}).then((data) => data.json())
}
const updateProfileImages = ({ credentials, avatar = null, banner = null, background = null }) => {
const updateProfileImages = ({ credentials, avatar = null, avatarName = null, banner = null, background = null }) => {
const form = new FormData()
if (avatar !== null) form.append('avatar', avatar)
if (avatar !== null) {
if (avatarName !== null) {
form.append('avatar', avatar, avatarName)
} else {
form.append('avatar', avatar)
}
}
if (banner !== null) form.append('header', banner)
if (background !== null) form.append('pleroma_background_image', background)
return fetch(MASTODON_PROFILE_UPDATE_URL, {
@ -191,6 +197,7 @@ const updateProfile = ({ credentials, params }) => {
// homepage
// location
// token
// language
const register = ({ params, credentials }) => {
const { nickname, ...rest } = params
return fetch(MASTODON_REGISTRATION_URL, {

View File

@ -1,12 +1,35 @@
import languagesObject from '../../i18n/messages'
import ISO6391 from 'iso-639-1'
import _ from 'lodash'
const specialLanguageCodes = {
'ja_easy': 'ja',
'zh_Hant': 'zh-HANT'
'zh_Hant': 'zh-HANT',
'zh': 'zh-Hans'
}
const internalToBrowserLocale = code => specialLanguageCodes[code] || code
const internalToBackendLocale = code => internalToBrowserLocale(code).replace('_', '-')
const getLanguageName = (code) => {
const specialLanguageNames = {
'ja_easy': 'やさしいにほんご',
'zh': '简体中文',
'zh_Hant': '繁體中文'
}
const languageName = specialLanguageNames[code] || ISO6391.getNativeName(code)
const browserLocale = internalToBrowserLocale(code)
return languageName.charAt(0).toLocaleUpperCase(browserLocale) + languageName.slice(1)
}
const languages = _.map(languagesObject.languages, (code) => ({ code: code, name: getLanguageName(code) })).sort((a, b) => a.name.localeCompare(b.name))
const localeService = {
internalToBrowserLocale
internalToBrowserLocale,
internalToBackendLocale,
languages,
getLanguageName
}
export default localeService

View File

@ -14,11 +14,12 @@ export const visibleTypes = store => {
rootState.config.notificationVisibility.follows && 'follow',
rootState.config.notificationVisibility.followRequest && 'follow_request',
rootState.config.notificationVisibility.moves && 'move',
rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction'
rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction',
rootState.config.notificationVisibility.polls && 'poll'
].filter(_ => _))
}
const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction']
const statusNotifications = ['like', 'mention', 'repeat', 'pleroma:emoji_reaction', 'poll']
export const isStatusNotification = (type) => includes(statusNotifications, type)
@ -98,6 +99,9 @@ export const prepareNotificationObject = (notification, i18n) => {
case 'follow_request':
i18nString = 'follow_request'
break
case 'poll':
i18nString = 'poll_ended'
break
}
if (notification.type === 'pleroma:emoji_reaction') {

View File

@ -1559,10 +1559,10 @@
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-2.0.0-rc.17.tgz#e6dcf5b5bd3ae23595bdb154b9b578ebcdffd698"
integrity sha512-7LHZKsFRV/HqDoMVY+cJamFzgHgsrmQFalROHC5FMWrzPzd+utG5e11krj1tVsnxYufGA2ABShX4nlcHXED+zQ==
"@vuelidate/core@2.0.0-alpha.35":
version "2.0.0-alpha.35"
resolved "https://registry.yarnpkg.com/@vuelidate/core/-/core-2.0.0-alpha.35.tgz#22d91787147b0883d31585fab44d0218622b7560"
integrity sha512-BSGQElu5lyI0GzqehFzUWy7GXhEUC7Z8oEpdxgCyGGN5gOFlAQ5Zr4dDFzfIOhU4jik3CfPHK+i+Juqg2OCKNw==
"@vuelidate/core@2.0.0-alpha.41":
version "2.0.0-alpha.41"
resolved "https://registry.yarnpkg.com/@vuelidate/core/-/core-2.0.0-alpha.41.tgz#eb4644aa45755c20901b4b8d20e1fecfd5389142"
integrity sha512-fST7s5wiLW8ZNTexe8+7fDdBZYT7HjbuA43/XDtKTlHs1BMRDDaBoFLZbHSqmHisQvGXa7zLG9bvG8X5cHZaxg==
dependencies:
vue-demi "^0.12.0"
@ -5722,6 +5722,11 @@ js-base64@^2.1.9:
version "2.5.0"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.0.tgz#42255ba183ab67ce59a0dee640afdc00ab5ae93e"
js-cookie@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.1.tgz#9e39b4c6c2f56563708d7d31f6f5f21873a92414"
integrity sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
@ -6223,6 +6228,11 @@ lodash.isequal@^4.2.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
lodash.isfunction@^3.0.8:
version "3.0.9"
resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051"
integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==
lodash.isplainobject@^3.0.0, lodash.isplainobject@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz#9a8238ae16b200432960cd7346512d0123fbf4c5"
@ -8671,20 +8681,20 @@ selenium-server@2.53.1:
version "2.53.1"
resolved "https://registry.yarnpkg.com/selenium-server/-/selenium-server-2.53.1.tgz#d681528812f3c2e0531a6b7e613e23bb02cce8a6"
"semver@2 || 3 || 4 || 5", semver@5.6.0, semver@^5.3.0, semver@^5.5.0, semver@^5.6.0:
"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0:
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
semver@5.7.1, semver@^5.4.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
semver@7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
semver@^5.4.1:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
semver@^5.5.1:
version "5.7.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
@ -10093,13 +10103,14 @@ webpack-log@^2.0.0:
ansi-colors "^3.0.0"
uuid "^3.3.2"
webpack-merge@0.14.1:
version "0.14.1"
resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-0.14.1.tgz#d6bfe6d9360a024e1e7f8e6383ae735f1737cd23"
integrity sha1-1r/m2TYKAk4ef45jg65zXxc3zSM=
webpack-merge@0.20.0:
version "0.20.0"
resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-0.20.0.tgz#e4b73429517181a287c59c8cafef5fc9eb1d9705"
integrity sha1-5Lc0KVFxgaKHxZyMr+9fyesdlwU=
dependencies:
lodash.find "^3.2.1"
lodash.isequal "^4.2.0"
lodash.isfunction "^3.0.8"
lodash.isplainobject "^3.2.0"
lodash.merge "^3.3.2"