diff --git a/CHANGELOG.md b/CHANGELOG.md index ac4e0a920543655ee4bdcc667b77ddb94c0f4297..e3c7f13219e3fc320a503b3bb8a07eee24bf7501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Ability to see local statuses in Statuses by instance section - Ability to configure Oban.Cron settings and settings for notifications streamer +- Settings search ### Fixed diff --git a/src/store/modules/normalizers.js b/src/store/modules/normalizers.js index 4d23c957490725e8ad9f3d619db6d442f22a6da1..6eecebca359a9b53356e3ac0019f63438bb7fd90 100644 --- a/src/store/modules/normalizers.js +++ b/src/store/modules/normalizers.js @@ -267,3 +267,25 @@ const wrapValues = (settings, currentState) => { } }) } + +export const formSearchObject = description => { + const parseNestedSettings = (description, label, key) => description.reduce((acc, setting) => { + const searchArray = _.compact([setting.key, setting.label, setting.description]).map(el => el.toLowerCase()) + const resultObject = { label: setting.label, key: setting.key || setting.group, groupKey: key, groupLabel: label, search: searchArray } + if (setting.children) { + const updatedAcc = [...acc, resultObject] + return [...updatedAcc, ...parseNestedSettings(setting.children, label, key)] + } + return [...acc, resultObject] + }, []) + + return description.reduce((acc, setting) => { + const searchArray = _.compact([setting.key, setting.label, setting.description]).map(el => el.toLowerCase()) + const resultObject = { label: setting.label, key: setting.key || setting.group, groupKey: setting.key || setting.group, groupLabel: setting.label, search: searchArray } + if (setting.children) { + const updatedAcc = !setting.key && setting.group === ':pleroma' ? acc : [...acc, resultObject] + return [...updatedAcc, ...parseNestedSettings(setting.children, setting.label, setting.key || setting.group)] + } + return !setting.key && setting.group === ':pleroma' ? acc : [...acc, resultObject] + }, []) +} diff --git a/src/store/modules/settings.js b/src/store/modules/settings.js index ab7a3b361bdf714e4cd336953c8f77c3cfccf0f7..5adc16a0df31865d590ec8ae611461fad2b3f399 100644 --- a/src/store/modules/settings.js +++ b/src/store/modules/settings.js @@ -1,5 +1,5 @@ import { fetchDescription, fetchSettings, removeSettings, restartApp, updateSettings } from '@/api/settings' -import { checkPartialUpdate, parseNonTuples, parseTuples, valueHasTuples, wrapUpdatedSettings } from './normalizers' +import { checkPartialUpdate, formSearchObject, parseNonTuples, parseTuples, valueHasTuples, wrapUpdatedSettings } from './normalizers' import _ from 'lodash' const settings = { @@ -10,6 +10,7 @@ const settings = { description: [], loading: true, needReboot: false, + searchData: {}, settings: {}, updatedSettings: {} }, @@ -32,6 +33,9 @@ const settings = { SET_LOADING: (state, status) => { state.loading = status }, + SET_SEARCH: (state, searchObject) => { + state.searchData = searchObject + }, SET_SETTINGS: (state, data) => { const newSettings = data.reduce((acc, { group, key, value }) => { const parsedValue = valueHasTuples(key, value) @@ -77,6 +81,8 @@ const settings = { const response = await fetchSettings(getters.authHost, getters.token) const description = await fetchDescription(getters.authHost, getters.token) commit('SET_DESCRIPTION', description.data) + const searchObject = formSearchObject(description.data) + commit('SET_SEARCH', searchObject) commit('SET_SETTINGS', response.data.configs) commit('TOGGLE_REBOOT', response.data.need_reboot) } catch (_e) { diff --git a/src/views/invites/index.vue b/src/views/invites/index.vue index 60111430f2d113b6a57da0ccbd653db1fd3c407d..55aa6fc09956aedca59d7b0cdb42084b99ffcf12 100644 --- a/src/views/invites/index.vue +++ b/src/views/invites/index.vue @@ -39,7 +39,7 @@ </el-form-item> </el-form> <span slot="footer"> - <el-button @click="closeDialogWindow">{{ $t('invites.cancel') }}</el-button> + <el-button class="invites-close-dialog" @click="closeDialogWindow">{{ $t('invites.cancel') }}</el-button> <el-button type="primary" @click="createToken">{{ $t('invites.create') }}</el-button> </span> <el-card v-if="'token' in newToken"> diff --git a/src/views/settings/components/ActivityPub.vue b/src/views/settings/components/ActivityPub.vue index abd03f10ffed71e9b14c13d0f352637cfa93c5c8..3e71e0c2f787eb709e55e3c4afec37ec0710ffca 100644 --- a/src/views/settings/components/ActivityPub.vue +++ b/src/views/settings/components/ActivityPub.vue @@ -1,10 +1,10 @@ <template> <div v-if="!loading" class="form-container"> - <el-form ref="activitypubData" :model="activitypubData" :label-width="labelWidth"> + <el-form ref="activitypubData" :model="activitypubData" :label-width="labelWidth" data-search=":activitypub"> <setting :setting-group="activitypub" :data="activitypubData"/> </el-form> <el-divider class="divider thick-line"/> - <el-form ref="userData" :model="userData" :label-width="labelWidth"> + <el-form ref="userData" :model="userData" :label-width="labelWidth" data-search=":user"> <setting :setting-group="user" :data="userData"/> </el-form> <div class="submit-button-container"> diff --git a/src/views/settings/components/Frontend.vue b/src/views/settings/components/Frontend.vue index 188a74cae304356bd9e42f58a5a14dd13c1111da..2dcacaaba13ff2c8e3f0af1d43629bb5877816f3 100644 --- a/src/views/settings/components/Frontend.vue +++ b/src/views/settings/components/Frontend.vue @@ -15,7 +15,7 @@ </el-form> <el-divider class="divider thick-line"/> <el-form ref="emojiData" :model="emojiData" :label-width="labelWidth"> - <el-form-item class="grouped-settings-header"> + <el-form-item data-search=":emoji" class="grouped-settings-header"> <span class="label-font">{{ $t('settings.emoji') }}</span> </el-form-item> <setting :setting-group="emoji" :data="emojiData"/> @@ -26,7 +26,7 @@ </el-form> <el-divider class="divider thick-line"/> <el-form ref="markupData" :model="markupData" :label-width="labelWidth"> - <el-form-item class="grouped-settings-header"> + <el-form-item data-search=":markup" class="grouped-settings-header"> <span class="label-font">{{ $t('settings.markup') }}</span> </el-form-item> <setting :setting-group="markup" :data="markupData"/> diff --git a/src/views/settings/components/Http.vue b/src/views/settings/components/Http.vue index a94000b274e8027a8b0370faadda89f54f970fac..d14cced4cc9c06a7a558cdb5bf36a8c1b478b1c4 100644 --- a/src/views/settings/components/Http.vue +++ b/src/views/settings/components/Http.vue @@ -4,7 +4,7 @@ <setting :setting-group="http" :data="httpData"/> </el-form> <el-form ref="corsPlugData" :model="corsPlugData" :label-width="labelWidth"> - <el-form-item class="grouped-settings-header"> + <el-form-item data-search=":cors_plug" class="grouped-settings-header"> <span class="label-font">{{ $t('settings.corsPlug') }}</span> </el-form-item> <setting :setting-group="corsPlug" :data="corsPlugData"/> diff --git a/src/views/settings/components/Inputs.vue b/src/views/settings/components/Inputs.vue index 300f09c7b39a708f0503c06480d360657afc8f31..981270cf109a1846095a9514e80a3b102cf6a683 100644 --- a/src/views/settings/components/Inputs.vue +++ b/src/views/settings/components/Inputs.vue @@ -1,7 +1,11 @@ <template> <div class="input-container"> <div v-if="setting.type === 'keyword'" class="keyword-container"> - <el-form-item :label-width="customLabelWidth" :class="labelClass" :style="`margin-left:${margin}px;margin-bottom:0`" > + <el-form-item + :label-width="customLabelWidth" + :class="labelClass" + :style="`margin-left:${margin}px;margin-bottom:0`" + :data-search="setting.key || setting.group"> <span slot="label"> {{ setting.label }} <el-tooltip v-if="canBeDeleted && isDesktop" :content="$t('settings.removeFromDB')" placement="bottom-end"> @@ -33,11 +37,13 @@ v-if="setting.type === 'string' || (setting.type.includes('string') && setting.type.includes('atom'))" :value="inputValue" :placeholder="setting.suggestions ? setting.suggestions[0] : null" + :data-search="setting.key || setting.group" class="input" @input="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"/> <el-switch v-if="setting.type === 'boolean'" :value="inputValue" + :data-search="setting.key || setting.group" class="switch-input" @change="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"/> <el-input-number @@ -46,10 +52,12 @@ :placeholder="setting.suggestions ? setting.suggestions[0].toString() : null" :min="0" :size="isDesktop ? 'large' : 'medium'" + :data-search="setting.key || setting.group" @change="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"/> <el-select v-if="setting.type === 'module' || (setting.type.includes('atom') && setting.type.includes('dropdown'))" :value="inputValue === false ? 'false' : inputValue" + :data-search="setting.key || setting.group" clearable class="input" @change="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"> @@ -61,6 +69,7 @@ <el-select v-if="renderMultipleSelect(setting.type)" :value="setting.key === ':rewrite_policy' ? rewritePolicyValue : inputValue" + :data-search="setting.key || setting.group" multiple filterable allow-create @@ -71,6 +80,7 @@ <el-input v-if="setting.key === ':ip'" :value="inputValue" + :data-search="setting.key || setting.group" placeholder="xxx.xxx.xxx.xx" class="input" @input="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"/> @@ -78,6 +88,7 @@ v-if="setting.type === 'atom'" :value="inputValue" :placeholder="setting.suggestions[0] ? setting.suggestions[0].substr(1) : ''" + :data-search="setting.key || setting.group" class="input" @input="update($event, settingGroup.group, settingGroup.key, settingParent, setting.key, setting.type, nested)"> <template slot="prepend">:</template> diff --git a/src/views/settings/components/Instance.vue b/src/views/settings/components/Instance.vue index a8698350c0eb5593b020b3230cc5ba7609a6c49a..a426323eadc25ff037109daad467510d96690b30 100644 --- a/src/views/settings/components/Instance.vue +++ b/src/views/settings/components/Instance.vue @@ -20,7 +20,7 @@ <setting :setting-group="manifest" :data="manifestData"/> </el-form> <el-divider class="divider thick-line"/> - <el-form ref="pleromaUser" :model="pleromaUserData" :label-width="labelWidth"> + <el-form ref="pleromaUser" :model="pleromaUserData" :label-width="labelWidth" data-search="Pleroma.User"> <setting :setting-group="pleromaUser" :data="pleromaUserData"/> </el-form> <el-divider class="divider thick-line"/> diff --git a/src/views/settings/components/Mailer.vue b/src/views/settings/components/Mailer.vue index 1624eff2c2ece24547e631972cce5bdc01887992..4938479835d41dcfee0cf0391ced63d891ee6c2d 100644 --- a/src/views/settings/components/Mailer.vue +++ b/src/views/settings/components/Mailer.vue @@ -11,9 +11,12 @@ <el-form ref="emailNotifications" :model="emailNotificationsData" :label-width="labelWidth"> <setting :setting-group="emailNotifications" :data="emailNotificationsData"/> </el-form> - <el-form ref="userEmail" :model="userEmail" :label-width="labelWidth"> + <el-form ref="userEmail" :model="userEmailData" :label-width="labelWidth"> <setting :setting-group="userEmail" :data="userEmailData"/> </el-form> + <el-form ref="newUsersDigestEmail" :model="newUsersDigestEmailData" :label-width="labelWidth"> + <setting :setting-group="newUsersDigestEmail" :data="newUsersDigestEmailData"/> + </el-form> <div class="submit-button-container"> <el-button class="submit-button" type="primary" @click="onSubmit">Submit</el-button> </div> @@ -65,6 +68,12 @@ export default { mailerData() { return _.get(this.settings.settings, [':pleroma', 'Pleroma.Emails.Mailer']) || {} }, + newUsersDigestEmail() { + return this.settings.description.find(setting => setting.key === 'Pleroma.Emails.NewUsersDigestEmail') + }, + newUsersDigestEmailData() { + return _.get(this.settings.settings, [':pleroma', 'Pleroma.Emails.NewUsersDigestEmail']) || {} + }, swoosh() { return this.settings.description.find(setting => setting.group === ':swoosh') }, diff --git a/src/views/settings/components/Setting.vue b/src/views/settings/components/Setting.vue index d676da299e07790fb248b8fe92e44c0848d27315..9794cc632aa65da2a61686a4a05296aaa3408123 100644 --- a/src/views/settings/components/Setting.vue +++ b/src/views/settings/components/Setting.vue @@ -1,6 +1,6 @@ <template> <div v-if="!loading"> - <el-form-item v-if="settingGroup.description" class="description-container"> + <el-form-item v-if="settingGroup.description" :data-search="settingGroup.key || settingGroup.group" class="description-container"> <span class="description" v-html="getFormattedDescription(settingGroup.description)"/> </el-form-item> <div v-if="settingGroup.key === 'Pleroma.Emails.Mailer'"> @@ -39,7 +39,7 @@ </div> <div v-else> <div class="input-container"> - <el-form-item class="grouped-settings-header"> + <el-form-item :data-search="setting.key || setting.group" class="grouped-settings-header"> <span slot="label"> <el-tooltip v-if="isDesktop && canBeDeleted(setting.key)" :content="$t('settings.removeFromDB')" placement="bottom-end"> <el-button icon="el-icon-delete" circle size="mini" style="margin-left:5px" @click="removeSetting(setting.key)"/> diff --git a/src/views/settings/components/inputComponents/AutoLinkerInput.vue b/src/views/settings/components/inputComponents/AutoLinkerInput.vue index 756eeb1c1135debb2398136462ec33b8548e2384..b14f914e132b5a3be5f2039ace78efbdc14e78cc 100644 --- a/src/views/settings/components/inputComponents/AutoLinkerInput.vue +++ b/src/views/settings/components/inputComponents/AutoLinkerInput.vue @@ -1,10 +1,10 @@ <template> <div> - <div v-if="setting.key === ':class' || setting.key === ':rel'"> + <div v-if="setting.key === ':class' || setting.key === ':rel'" :data-search="setting.key || setting.group"> <el-switch :value="autoLinkerBooleanValue(setting.key)" @change="processTwoTypeValue($event, setting.key)"/> <el-input v-if="autoLinkerBooleanValue(setting.key)" :value="autoLinkerStringValue(setting.key)" @input="processTwoTypeValue($event, setting.key)"/> </div> - <div v-if="setting.key === ':truncate'"> + <div v-if="setting.key === ':truncate'" :data-search="setting.key || setting.group"> <el-switch :value="autoLinkerBooleanValue(setting.key)" @change="processTwoTypeValue($event, setting.key)"/> <el-input-number v-if="autoLinkerBooleanValue(setting.key)" :value="autoLinkerIntegerValue(setting.key)" @input="processTwoTypeValue($event, setting.key)"/> </div> diff --git a/src/views/settings/components/inputComponents/CrontabInput.vue b/src/views/settings/components/inputComponents/CrontabInput.vue index f3f16d26aef333e16800d743c0315a113c116305..89a1491549784d8c0295c047d1ac3e21e774089d 100644 --- a/src/views/settings/components/inputComponents/CrontabInput.vue +++ b/src/views/settings/components/inputComponents/CrontabInput.vue @@ -1,6 +1,6 @@ <template> <el-form :label-width="labelWidth" :label-position="isMobile ? 'top' : 'right'" class="crontab"> - <el-form-item v-for="worker in workers" :key="worker" :label="worker" class="crontab-container"> + <el-form-item v-for="worker in workers" :key="worker" :label="worker" :data-search="setting.key" class="crontab-container"> <el-input :value="data[worker]" :placeholder="getSuggestion(worker) || null" diff --git a/src/views/settings/components/inputComponents/EditableKeywordInput.vue b/src/views/settings/components/inputComponents/EditableKeywordInput.vue index fe5b9c7b24ef962b7efa6d736855f0be55417fdb..009b2033bcf750d296a6471e98466e085b89c318 100644 --- a/src/views/settings/components/inputComponents/EditableKeywordInput.vue +++ b/src/views/settings/components/inputComponents/EditableKeywordInput.vue @@ -1,6 +1,6 @@ <template> <div class="editable-keyword-container"> - <div v-if="setting.key === ':replace'"> + <div v-if="setting.key === ':replace'" :data-search="setting.key || setting.group"> <div v-for="element in data" :key="getId(element)" class="setting-input"> <el-input :value="getKey(element)" placeholder="pattern" class="name-input" @input="parseEditableKeyword($event, 'key', element)"/> : <el-input :value="getValue(element)" placeholder="replacement" class="value-input" @input="parseEditableKeyword($event, 'value', element)"/> @@ -8,7 +8,7 @@ </div> <el-button :size="isDesktop ? 'medium' : 'mini'" icon="el-icon-plus" circle @click="addRowToEditableKeyword"/> </div> - <div v-else-if="editableKeywordWithInteger"> + <div v-else-if="editableKeywordWithInteger" :data-search="setting.key || setting.group"> <div v-for="element in data" :key="getId(element)" class="setting-input"> <el-input :value="getKey(element)" placeholder="key" class="name-input" @input="parseEditableKeyword($event, 'key', element)"/> : <el-input-number :value="getValue(element)" :min="0" size="large" class="value-input" @change="parseEditableKeyword($event, 'value', element)"/> @@ -16,7 +16,7 @@ </div> <el-button :size="isDesktop ? 'medium' : 'mini'" icon="el-icon-plus" circle @click="addRowToEditableKeyword"/> </div> - <div v-else> + <div v-else :data-search="setting.key || setting.group"> <div v-for="element in data" :key="getId(element)" class="setting-input"> <el-input :value="getKey(element)" placeholder="key" class="name-input" @input="parseEditableKeyword($event, 'key', element)"/> : <el-select :value="getValue(element)" multiple filterable allow-create class="value-input" @change="parseEditableKeyword($event, 'value', element)"/> diff --git a/src/views/settings/components/inputComponents/IconsInput.vue b/src/views/settings/components/inputComponents/IconsInput.vue index f5278bbae7688ffda528ac0e6eadfb36270e2db2..e2f091656dd6566b9518870517dce483d9665cdd 100644 --- a/src/views/settings/components/inputComponents/IconsInput.vue +++ b/src/views/settings/components/inputComponents/IconsInput.vue @@ -1,5 +1,5 @@ <template> - <div class="mascot-container"> + <div :data-search="setting.key || setting.group" class="mascot-container"> <div v-for="(icon, index) in data" :key="index" class="mascot"> <div class="icons-container"> <div class="icon-container"> diff --git a/src/views/settings/components/inputComponents/MascotsInput.vue b/src/views/settings/components/inputComponents/MascotsInput.vue index 902ed22fb22cf89ee014b97adb2ebebfe41ef738..e9d94e512c80d478cc1e6a0f6183299ceff9d900 100644 --- a/src/views/settings/components/inputComponents/MascotsInput.vue +++ b/src/views/settings/components/inputComponents/MascotsInput.vue @@ -1,5 +1,5 @@ <template> - <div class="mascot-container"> + <div :data-search="setting.key || setting.group" class="mascot-container"> <div v-for="mascot in data" :key="getId(mascot)" class="mascot"> <el-form-item label="Name" label-width="85px" class="mascot-form-item"> <div class="mascot-name-container"> diff --git a/src/views/settings/components/inputComponents/MultipleSelect.vue b/src/views/settings/components/inputComponents/MultipleSelect.vue index ead60d694b07b71f9621e525b2f49947294992af..66b3aa7546babdc5d6868ec2c9da0b9c00722ae3 100644 --- a/src/views/settings/components/inputComponents/MultipleSelect.vue +++ b/src/views/settings/components/inputComponents/MultipleSelect.vue @@ -3,6 +3,7 @@ <el-select v-if="setting.key === ':backends'" :value="data.value" + :data-search="setting.key || setting.group" multiple filterable allow-create @@ -15,6 +16,7 @@ <el-select v-if="setting.key === ':args'" :value="data[setting.key]" + :data-search="setting.key || setting.group" multiple filterable allow-create diff --git a/src/views/settings/components/inputComponents/ProxyUrlInput.vue b/src/views/settings/components/inputComponents/ProxyUrlInput.vue index 84956074ae49517645a63637913f218aaa05c7bc..c2ee2381d3cec829bf9e1f67091c81f6811c27a2 100644 --- a/src/views/settings/components/inputComponents/ProxyUrlInput.vue +++ b/src/views/settings/components/inputComponents/ProxyUrlInput.vue @@ -1,5 +1,5 @@ <template> - <div class="proxy-url-input"> + <div :data-search="setting.key || setting.group" class="proxy-url-input"> <el-input :value="proxyUrlData.host" placeholder="host (e.g. localhost or 127.0.0.1)" diff --git a/src/views/settings/components/inputComponents/PruneInput.vue b/src/views/settings/components/inputComponents/PruneInput.vue index c851aa5b237f874caacf90da1c93a328559f9ba6..7c17415da64e359396cf55200f0a493f522c55d5 100644 --- a/src/views/settings/components/inputComponents/PruneInput.vue +++ b/src/views/settings/components/inputComponents/PruneInput.vue @@ -1,5 +1,5 @@ <template> - <div> + <div :data-search="setting.key || setting.group"> <el-radio-group v-model="prune" class="prune-options"> <el-radio label=":disabled">Disabled</el-radio> <el-radio label=":maxlen">Limit-based</el-radio> diff --git a/src/views/settings/components/inputComponents/RateLimitInput.vue b/src/views/settings/components/inputComponents/RateLimitInput.vue index caddd15c1c15d680831402be2d5926e8d4f8ca43..431ae83b48d7efcc8e1c54aa07ba67787c8c35b4 100644 --- a/src/views/settings/components/inputComponents/RateLimitInput.vue +++ b/src/views/settings/components/inputComponents/RateLimitInput.vue @@ -1,5 +1,5 @@ <template> - <div class="rate-limit-container"> + <div :data-search="setting.key || setting.group" class="rate-limit-container"> <div v-if="!rateLimitAuthUsers"> <el-input :value="rateLimitAllUsers[0]" diff --git a/src/views/settings/components/tabs.js b/src/views/settings/components/tabs.js new file mode 100644 index 0000000000000000000000000000000000000000..4bfa205113d6cd26e56b04989ae13d83347b21c3 --- /dev/null +++ b/src/views/settings/components/tabs.js @@ -0,0 +1,82 @@ +export const tabs = { + 'activity-pub': { + label: 'settings.activityPub', + settings: [':activitypub', ':user'] + }, + 'authentication': { + label: 'settings.auth', + settings: [':auth', ':ldap', ':oauth2', 'Pleroma.Web.Auth.Authenticator'] + }, + 'auto-linker': { + label: 'settings.autoLinker', + settings: [':opts'] + }, + 'esshd': { + label: 'settings.esshd', + settings: [':esshd'] + }, + 'captcha': { + label: 'settings.captcha', + settings: ['Pleroma.Captcha', 'Pleroma.Captcha.Kocaptcha'] + }, + 'frontend': { + label: 'settings.frontend', + settings: [':assets', ':chat', ':emoji', ':frontend_configurations', ':markup', ':static_fe'] + }, + 'gopher': { + label: 'settings.gopher', + settings: [':gopher'] + }, + 'http': { + label: 'settings.http', + settings: [':cors_plug', ':http', ':http_security', ':http_signatures', ':web_cache_ttl'] + }, + 'instance': { + label: 'settings.instance', + settings: [':admin_token', ':fetch_initial_posts', ':instance', ':manifest', 'Pleroma.User', 'Pleroma.ScheduledActivity', ':uri_schemes', ':feed', ':streamer'] + }, + 'job-queue': { + label: 'settings.jobQueue', + settings: ['Pleroma.ActivityExpiration', 'Oban', ':workers'] + }, + 'logger': { + label: 'settings.logger', + settings: [':console', ':ex_syslogger', ':quack', ':logger'] + }, + 'mailer': { + label: 'settings.mailer', + settings: [':email_notifications', 'Pleroma.Emails.Mailer', 'Pleroma.Emails.UserEmail', ':swoosh', 'Pleroma.Emails.NewUsersDigestEmail'] + }, + 'media-proxy': { + label: 'settings.mediaProxy', + settings: [':media_proxy'] + }, + 'metadata': { + label: 'settings.metadata', + settings: ['Pleroma.Web.Metadata', ':rich_media'] + }, + 'mrf': { + label: 'settings.mrf', + settings: [':mrf_simple', ':mrf_rejectnonpublic', ':mrf_hellthread', ':mrf_keyword', ':mrf_subchain', ':mrf_mention', ':mrf_normalize_markup', ':mrf_vocabulary', ':mrf_object_age', ':modules'] + }, + 'rate-limiters': { + label: 'settings.rateLimiters', + settings: [':rate_limit'] + }, + 'relays': { + label: 'settings.relays', + settings: [] + }, + 'web-push': { + label: 'settings.webPush', + settings: [':vapid_details'] + }, + 'upload': { + label: 'settings.upload', + settings: ['Pleroma.Upload.Filter.AnonymizeFilename', 'Pleroma.Upload.Filter.Mogrify', 'Pleroma.Uploaders.S3', 'Pleroma.Uploaders.Local', 'Pleroma.Upload'] + }, + 'other': { + label: 'settings.other', + settings: [':mime', 'Pleroma.Plugs.RemoteIp'] + } +} diff --git a/src/views/settings/index.vue b/src/views/settings/index.vue index 3d0bbcdb9807a570be5b16f404d01ccb6dd3de21..048939a354590aeeefc4cf8ba16a489045563e5d 100644 --- a/src/views/settings/index.vue +++ b/src/views/settings/index.vue @@ -23,68 +23,26 @@ </span> </el-button> </el-link> + <el-autocomplete + v-model="searchQuery" + :fetch-suggestions="querySearch" + :trigger-on-focus="false" + clearable + placeholder="Search" + prefix-icon="el-icon-search" + class="settings-search-input" + @select="handleSearchSelect"/> </div> </div> <el-tabs v-model="activeTab" tab-position="left"> - <el-tab-pane :label="$t('settings.activityPub')" :disabled="configDisabled" name="activityPub" lazy> - <activity-pub/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.auth')" :disabled="configDisabled" name="auth" lazy> - <authentication/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.autoLinker')" :disabled="configDisabled" name="autoLinker" lazy> - <auto-linker/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.esshd')" :disabled="configDisabled" name="esshd" lazy> - <esshd/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.captcha')" :disabled="configDisabled" name="captcha" lazy> - <captcha/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.frontend')" :disabled="configDisabled" name="frontend" lazy> - <frontend/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.gopher')" :disabled="configDisabled" name="gopher" lazy> - <gopher/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.http')" :disabled="configDisabled" name="http" lazy> - <http/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.instance')" :disabled="configDisabled" name="instance"> - <instance/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.jobQueue')" :disabled="configDisabled" name="jobQueue" lazy> - <job-queue/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.logger')" :disabled="configDisabled" name="logger" lazy> - <logger/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.mailer')" :disabled="configDisabled" name="mailer" lazy> - <mailer/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.mediaProxy')" :disabled="configDisabled" name="mediaProxy" lazy> - <media-proxy/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.metadata')" :disabled="configDisabled" name="metadata" lazy> - <metadata/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.mrf')" :disabled="configDisabled" name="mrf" lazy> - <mrf/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.rateLimiters')" :disabled="configDisabled" name="rateLimiters" lazy> - <rate-limiters/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.relays')" lazy name="relays"> - <relays/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.webPush')" :disabled="configDisabled" name="webPush" lazy> - <web-push/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.upload')" :disabled="configDisabled" name="upload" lazy> - <upload/> - </el-tab-pane> - <el-tab-pane :label="$t('settings.other')" :disabled="configDisabled" name="other" lazy> - <other/> + <el-tab-pane + v-for="(value, componentName) in tabs" + :label="$t(value.label)" + :disabled="configDisabled" + :key="componentName" + :name="componentName" + lazy> + <component :is="componentName"/> </el-tab-pane> </el-tabs> </div> @@ -119,6 +77,7 @@ </el-button> </el-link> </div> + <div class="settings-search-input-container"/> <activity-pub v-if="activeTab === 'activityPub'"/> <authentication v-if="activeTab === 'auth'"/> <auto-linker v-if="activeTab === 'autoLinker'"/> @@ -145,6 +104,7 @@ <script> import i18n from '@/lang' +import { tabs } from './components/tabs' import { ActivityPub, Authentication, @@ -214,7 +174,8 @@ export default { { value: 'webPush', label: i18n.t('settings.webPush') }, { value: 'upload', label: i18n.t('settings.upload') }, { value: 'other', label: i18n.t('settings.other') } - ] + ], + searchQuery: '' } }, computed: { @@ -240,6 +201,12 @@ export default { }, needReboot() { return this.$store.state.settings.needReboot + }, + searchData() { + return this.$store.state.settings.searchData + }, + tabs() { + return tabs } }, mounted: function() { @@ -256,6 +223,25 @@ export default { type: 'success', message: i18n.t('settings.restartSuccess') }) + }, + async handleSearchSelect(selectedValue) { + const tab = Object.keys(this.tabs).find(tab => { + return this.tabs[tab].settings.includes(selectedValue.group === ':pleroma' ? selectedValue.key : selectedValue.group) + }) + await this.$store.dispatch('SetActiveTab', tab) + const selectedSetting = document.querySelector(`[data-search="${selectedValue.key}"]`) + if (selectedSetting) { + selectedSetting.scrollIntoView({ block: 'start', behavior: 'smooth' }) + } + }, + querySearch(queryString, cb) { + const results = this.searchData.filter(searchObj => searchObj.search.find(el => el.includes(queryString.toLowerCase()))) + .map(searchObj => { + return searchObj.groupKey === ':opts' + ? { value: `${searchObj.label} in Auto Linker`, group: searchObj.groupKey, key: searchObj.key } + : { value: `${searchObj.label} in ${searchObj.groupLabel}`, group: searchObj.groupKey, key: searchObj.key } + }) + cb(results) } } } diff --git a/src/views/settings/styles/main.scss b/src/views/settings/styles/main.scss index 8e6cb4cf4e63cc25cf63d30b356e420855869869..884ad08e025f2db04dd21e5eb79841c11314ceec 100644 --- a/src/views/settings/styles/main.scss +++ b/src/views/settings/styles/main.scss @@ -277,6 +277,10 @@ padding: 10px; margin-right: 5px; } + .settings-search-input { + width: 350px; + margin-left: 5px; + } .single-input { margin-right: 10px } @@ -406,6 +410,13 @@ .limit-input { width: 45%; } + .nav-container { + display: flex; + height: 36px; + justify-content: space-between; + align-items: center; + margin: 15px; + } .proxy-url-input { flex-direction: column; align-items: flex-start; @@ -446,12 +457,12 @@ .settings-header-container { margin: 10px 15px 15px 15px; } - .nav-container { - display: flex; - height: 36px; - justify-content: space-between; - align-items: center; - margin: 15px; + .settings-search-input { + width: 100%; + margin-left: 0; + } + .settings-search-input-container { + margin: 0 15px 15px 15px; } .settings-menu { width: 163px; @@ -530,7 +541,6 @@ margin-right: 8px } } - @media only screen and (max-width:801px) and (min-width: 481px) { .delete-setting-button { margin: 4px 0 0 10px; @@ -575,5 +585,9 @@ .settings-delete-button { float: right; } + .settings-search-input { + width: 250px; + margin: 0 0 15px 15px; + } } } diff --git a/src/views/users/components/ModerationDropdown.vue b/src/views/users/components/ModerationDropdown.vue index e4a974c131659bf2811edd2672291e3465f85dc9..fad336c3d2324a6a317a0bd5434477a3e9b973e5 100644 --- a/src/views/users/components/ModerationDropdown.vue +++ b/src/views/users/components/ModerationDropdown.vue @@ -172,7 +172,7 @@ export default { <style rel='stylesheet/scss' lang='scss'> .moderate-user-button { text-align: left; - width: 200px; + width: 350px; padding: 10px; } .moderate-user-button-container { diff --git a/src/views/users/index.vue b/src/views/users/index.vue index d6b12e68d167d0e1102b19b305d2ca1e8bfd6c56..85be0bee322489bc7048fd25b1e17ce9cc7d935e 100644 --- a/src/views/users/index.vue +++ b/src/views/users/index.vue @@ -6,7 +6,12 @@ </h1> <div class="filter-container"> <users-filter/> - <el-input :placeholder="$t('users.search')" v-model="search" class="search" @input="handleDebounceSearchInput"/> + <el-input + :placeholder="$t('users.search')" + v-model="search" + prefix-icon="el-icon-search" + class="search" + @input="handleDebounceSearchInput"/> </div> <div class="actions-container"> <el-button class="actions-button" @click="createAccountDialogOpen = true"> diff --git a/test/views/invites/index.test.js b/test/views/invites/index.test.js index c4a1b4efe5e36f603bc871b15615ff4facc23e2b..6789a3a87334283205afafb7039b798dbcdb3f9d 100644 --- a/test/views/invites/index.test.js +++ b/test/views/invites/index.test.js @@ -44,19 +44,18 @@ describe('Invite tokens', () => { }) await flushPromises() - const dialog = wrapper.find('div.el-dialog__wrapper .create-new-token-dialog') - expect(dialog.isVisible()).toBe(false) - + wrapper.setData({ createTokenDialogVisible: false }) const openDialogButton = wrapper.find('button.create-invite-token') - const closeDialogButton = wrapper.find('div.el-dialog__footer button') + const closeDialogButton = wrapper.find('div.el-dialog__footer button.invites-close-dialog') + expect(wrapper.vm.createTokenDialogVisible).toBe(false) openDialogButton.trigger('click') await flushPromises() - expect(dialog.isVisible()).toBe(true) + expect(wrapper.vm.createTokenDialogVisible).toBe(true) closeDialogButton.trigger('click') await flushPromises() - expect(dialog.isVisible()).toBe(false) + expect(wrapper.vm.createTokenDialogVisible).toBe(false) done() }) diff --git a/test/views/reports/status.test.js b/test/views/reports/status.test.js index 3551cf7393421e3c9385ca1ea905d11be0f4155a..08423403dc6762d90b8d00385551429f67acd2aa 100644 --- a/test/views/reports/status.test.js +++ b/test/views/reports/status.test.js @@ -33,7 +33,8 @@ describe('Status in reports', () => { status, page: 1, userId: '7', - godmode: false + godmode: false, + showCheckbox: false } }) await flushPromises() @@ -58,7 +59,8 @@ describe('Status in reports', () => { status, page: 1, userId: '7', - godmode: false + godmode: false, + showCheckbox: false } }) await flushPromises() @@ -83,7 +85,8 @@ describe('Status in reports', () => { status, page: 1, userId: '7', - godmode: false + godmode: false, + showCheckbox: false } }) await flushPromises() @@ -108,7 +111,8 @@ describe('Status in reports', () => { status, page: 1, userId: '7', - godmode: false + godmode: false, + showCheckbox: false } }) await flushPromises() @@ -133,7 +137,8 @@ describe('Status in reports', () => { status, page: 1, userId: '7', - godmode: false + godmode: false, + showCheckbox: false } }) await flushPromises() diff --git a/test/views/settings/formSearchObject.test.js b/test/views/settings/formSearchObject.test.js new file mode 100644 index 0000000000000000000000000000000000000000..dbabbc872fc38fffa29e362b03071a07e0e71dab --- /dev/null +++ b/test/views/settings/formSearchObject.test.js @@ -0,0 +1,213 @@ +import { formSearchObject } from '@/store/modules/normalizers' +import _ from 'lodash' + +describe('Form search object', () => { + it('forms search object for regular setting', () => { + const description = [{ + description: "Upload general settings", + group: ":pleroma", + key: "Pleroma.Upload", + label: "Pleroma.Upload", + children: [ + { description: "Module which will be used for uploads", + key: ":uploader", + label: "Uploader" }, + { description: "List of filter modules for uploads", + key: ":filters", + label: "Filters" } + ] + }] + const expected = [ + { label: "Pleroma.Upload", + key: "Pleroma.Upload", + groupKey: "Pleroma.Upload", + groupLabel: "Pleroma.Upload", + search: [ + "pleroma.upload", + "pleroma.upload", + "upload general settings" + ] + }, + { label: "Uploader", + key: ":uploader", + groupKey: "Pleroma.Upload", + groupLabel: "Pleroma.Upload", + search: [ + ":uploader", + "uploader", + "module which will be used for uploads" + ] + }, + { label: "Filters", + key: ":filters", + groupKey: "Pleroma.Upload", + groupLabel: "Pleroma.Upload", + search: [ + ":filters", + "filters", + "list of filter modules for uploads" + ] + } + ] + expect(_.isEqual(formSearchObject(description), expected)).toBeTruthy() + }) + + it('forms search object for setting without key', () => { + const description = [{ + description: "`Swoosh.Adapters.Local` adapter specific settings", + group: ":swoosh", + label: "Swoosh", + children: [ + { description: "Run the preview server together as part of your app", + group: [":subgroup", "Swoosh.Adapters.Local"], + key: ":serve_mailbox", + label: "Serve mailbox" + } + ] + }] + const expected = [ + { label: "Swoosh", + key: ":swoosh", + groupKey: ":swoosh", + groupLabel: "Swoosh", + search: ["swoosh", "`swoosh.adapters.local` adapter specific settings"] + }, + { label: "Serve mailbox", + key: ":serve_mailbox", + groupKey: ":swoosh", + groupLabel: "Swoosh", + search: [ + ":serve_mailbox", + "serve mailbox", + "run the preview server together as part of your app" + ] + } + ] + expect(_.isEqual(formSearchObject(description), expected)).toBeTruthy() + }) + + it('forms search object for setting without key and description', () => { + const description = [{ + group: ":cors_plug", + label: "Cors plug", + children: [ + { key: ":max_age", + label: "Max age" }, + { key: ":methods", + label: "Methods" } + ] + }] + const expected = [ + { label: "Cors plug", + key: ":cors_plug", + groupKey: ":cors_plug", + groupLabel: "Cors plug", + search: ["cors plug"] + }, + { label: "Max age", + key: ":max_age", + groupKey: ":cors_plug", + groupLabel: "Cors plug", + search: [":max_age", "max age"] + }, + { label: "Methods", + key: ":methods", + groupKey: ":cors_plug", + groupLabel: "Cors plug", + search: [":methods", "methods"] + } + ] + expect(_.isEqual(formSearchObject(description), expected)).toBeTruthy() + }) + + it('forms search object for setting without key in pleroma group', () => { + const description = [{ + description: "Allows to set a token", + group: ":pleroma", + label: "Pleroma", + children: [ + { description: "Token", + key: ":admin_token", + label: "Admin token" } + ] + }] + const expected = [{ + label: "Admin token", + key: ":admin_token", + groupKey: ":pleroma", + groupLabel: "Pleroma", + search: [":admin_token", "admin token", "token"] + }] + expect(_.isEqual(formSearchObject(description), expected)).toBeTruthy() + }) + + it('forms search object for nested setting', () => { + const description = [{ + description: "Media proxy", + group: ":pleroma", + key: ":media_proxy", + label: "Media proxy", + children: [ + { description: "Options for Pleroma.ReverseProxy", + key: ":proxy_opts", + label: "Proxy opts", + children: [ + { description: "HTTP options", + key: ":http", + label: "Http", + children: [ + { description: "Adapter specific options", + key: ":adapter", + label: "Adapter", + children: [ + { description: "SSL options for HTTP adapter", + key: ":ssl_options", + label: "SSL Options" + } + ] + } + ] + } + ]} + ] + }] + const expected = [ + { + label: "Media proxy", + key: ":media_proxy", + groupKey: ":media_proxy", + groupLabel: "Media proxy", + search: [":media_proxy", "media proxy", "media proxy"] + }, + { + label: "Proxy opts", + key: ":proxy_opts", + groupKey: ":media_proxy", + groupLabel: "Media proxy", + search: [":proxy_opts", "proxy opts", "options for pleroma.reverseproxy"] + }, + { + label: "Http", + key: ":http", + groupKey: ":media_proxy", + groupLabel: "Media proxy", + search: [":http", "http", "http options"] + }, + { + label: "Adapter", + key: ":adapter", + groupKey: ":media_proxy", + groupLabel: "Media proxy", + search: [":adapter", "adapter", "adapter specific options"] + }, + { + label: "SSL Options", + key: ":ssl_options", + groupKey: ":media_proxy", + groupLabel: "Media proxy", + search: [":ssl_options", "ssl options", "ssl options for http adapter"] + } + ] + expect(_.isEqual(formSearchObject(description), expected)).toBeTruthy() + }) +}) diff --git a/test/views/settings/index.test.js b/test/views/settings/index.test.js new file mode 100644 index 0000000000000000000000000000000000000000..6e46b9e659c914aa77e30d2b1a30c621c1b303b3 --- /dev/null +++ b/test/views/settings/index.test.js @@ -0,0 +1,63 @@ +import Vuex from 'vuex' +import { mount, createLocalVue, config } from '@vue/test-utils' +import Element from 'element-ui' +import Settings from '@/views/settings/index' +import flushPromises from 'flush-promises' +import app from '@/store/modules/app' +import settings from '@/store/modules/settings' +import getters from '@/store/getters' + +config.mocks["$t"] = () => {} + +const localVue = createLocalVue() +localVue.use(Vuex) +localVue.use(Element) + +describe('Settings search', () => { + let store + let actions + + beforeEach(() => { + actions = { ...settings.actions, FetchSettings: jest.fn() } + store = new Vuex.Store({ + modules: { + app, + settings: { ...settings, actions } + }, + getters + }) + }) + + it('shows search input', async (done) => { + const wrapper = mount(Settings, { + store, + localVue + }) + + await flushPromises() + const searchInput = wrapper.find('.settings-search-input') + expect(searchInput.exists()).toBe(true) + done() + }) + it('changes tab when search value was selected', async (done) => { + const wrapper = mount(Settings, { + store, + localVue + }) + wrapper.vm.handleSearchSelect({ group: 'Pleroma.Upload', key: 'Pleroma.Upload' }) + expect(store.state.settings.activeTab).toBe('upload') + + wrapper.vm.handleSearchSelect({ group: ':swoosh', key: ':serve_mailbox' }) + expect(store.state.settings.activeTab).toBe('mailer') + + wrapper.vm.handleSearchSelect({ group: ':pleroma', key: ':admin_token' }) + expect(store.state.settings.activeTab).toBe('instance') + + wrapper.vm.handleSearchSelect({ group: ':media_proxy', key: ':ssl_options' }) + expect(store.state.settings.activeTab).toBe('media-proxy') + + wrapper.vm.handleSearchSelect({ group: ':opts', key: ':opts' }) + expect(store.state.settings.activeTab).toBe('auto-linker') + done() + }) +}) diff --git a/test/views/users/index.test.js b/test/views/users/index.test.js index 63473aaf9261e123735050abcc0153dc6a517ca3..0000421498a91f885d07dfeb4067eaf2edb9ba55 100644 --- a/test/views/users/index.test.js +++ b/test/views/users/index.test.js @@ -239,21 +239,21 @@ describe('Users actions', () => { }) await flushPromises() - const dialog = wrapper.find('.password-reset-token-dialog') + wrapper.setData({ resetPasswordDialogOpen: false }) const closeDialogButton = wrapper.find('.password-reset-token-dialog button') - expect(dialog.isVisible()).toBe(false) + expect(wrapper.vm.resetPasswordDialogOpen).toBe(false) expect(store.state.users.passwordResetToken.token).toBe('') wrapper.find(htmlElement(1, 11)).trigger('click') await flushPromises() - expect(dialog.isVisible()).toBe(true) + expect(wrapper.vm.resetPasswordDialogOpen).toBe(true) expect(store.state.users.passwordResetToken.token).toBe('g05lxnBJQnL') expect(store.state.users.passwordResetToken.link).toBe('http://url/api/pleroma/password_reset/g05lxnBJQnL') closeDialogButton.trigger('click') await flushPromises() - expect(dialog.isVisible()).toBe(false) + expect(wrapper.vm.resetPasswordDialogOpen).toBe(false) done() }) }) @@ -278,19 +278,18 @@ describe('Creates new account', () => { }) await flushPromises() - const dialog = wrapper.find('div.el-dialog__wrapper') - expect(dialog.isVisible()).toBe(false) - + wrapper.setData({ createAccountDialogOpen: false }) const openDialogButton = wrapper.find('button.actions-button') const closeDialogButton = wrapper.find('div.el-dialog__footer button') + expect(wrapper.vm.createAccountDialogOpen).toBe(false) openDialogButton.trigger('click') await flushPromises() - expect(dialog.isVisible()).toBe(true) + expect(wrapper.vm.createAccountDialogOpen).toBe(true) closeDialogButton.trigger('click') await flushPromises() - expect(dialog.isVisible()).toBe(false) + expect(wrapper.vm.createAccountDialogOpen).toBe(false) done() }) diff --git a/yarn.lock b/yarn.lock index 9a72fd1c2312b1a56abd645c9cbf1e96c6cf9d34..9f9b284d7310608a96199e85a2aa25658afaace9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1013,12 +1013,13 @@ vue-template-es2015-compiler "^1.9.0" "@vue/test-utils@^1.0.0-beta.29": - version "1.0.0-beta.29" - resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.29.tgz#c942cf25e891cf081b6a03332b4ae1ef430726f0" - integrity sha512-yX4sxEIHh4M9yAbLA/ikpEnGKMNBCnoX98xE1RwxfhQVcn0MaXNSj1Qmac+ZydTj6VBSEVukchBogXBTwc+9iA== + version "1.0.0-beta.32" + resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.0.0-beta.32.tgz#38c3947886236201a3f24b583c73598eb95ccc69" + integrity sha512-ywhe7PATMAk/ZGdsrcuQIliQusOyfe0OOHjKKCCERqgHh1g/kqPtmSMT5Jx4sErx53SYbNucr8QOK6/u5ianAw== dependencies: dom-event-types "^1.0.0" - lodash "^4.17.4" + lodash "^4.17.15" + pretty "^2.0.0" "@webassemblyjs/ast@1.8.5": version "1.8.5" @@ -2496,6 +2497,11 @@ commander@^2.13.0, commander@^2.14.1, commander@^2.9.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== +commander@^2.19.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -2568,6 +2574,23 @@ concat-stream@^1.5.0, concat-stream@^1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" +condense-newlines@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/condense-newlines/-/condense-newlines-0.2.1.tgz#3de985553139475d32502c83b02f60684d24c55f" + integrity sha1-PemFVTE5R10yUCyDsC9gaE0kxV8= + dependencies: + extend-shallow "^2.0.1" + is-whitespace "^0.3.0" + kind-of "^3.0.2" + +config-chain@^1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" + integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + connect-history-api-fallback@^1.3.0: version "1.6.0" resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" @@ -3431,6 +3454,16 @@ echarts@4.1.0: dependencies: zrender "4.0.4" +editorconfig@^0.15.3: + version "0.15.3" + resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" + integrity sha512-M9wIMFx96vq0R4F+gRpY3o2exzb8hEj/n9S8unZtHSvYjibBp/iMufSzvmOcV/laG0ZtuTVGtiJggPOSW2r93g== + dependencies: + commander "^2.19.0" + lru-cache "^4.1.5" + semver "^5.6.0" + sigmund "^1.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -4399,7 +4432,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -4411,6 +4444,18 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, gl once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -4934,16 +4979,21 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= +inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" @@ -5289,6 +5339,11 @@ is-utf8@^0.2.0: resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= +is-whitespace@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/is-whitespace/-/is-whitespace-0.3.0.tgz#1639ecb1be036aec69a54cbb401cfbed7114ab7f" + integrity sha1-Fjnssb4DauxppUy7QBz77XEUq38= + is-windows@^1.0.0, is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -5776,6 +5831,17 @@ js-base64@^2.1.8, js-base64@^2.1.9: resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.5.1.tgz#1efa39ef2c5f7980bb1784ade4a8af2de3291121" integrity sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw== +js-beautify@^1.6.12: + version "1.10.3" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.10.3.tgz#c73fa10cf69d3dfa52d8ed624f23c64c0a6a94c1" + integrity sha512-wfk/IAWobz1TfApSdivH5PJ0miIHgDoYb1ugSqHcODPmaYu46rYe5FVuIEkhjg8IQiv6rDNPyhsqbsohI/C2vQ== + dependencies: + config-chain "^1.1.12" + editorconfig "^0.15.3" + glob "^7.1.3" + mkdirp "~0.5.1" + nopt "~4.0.1" + js-cookie@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.0.tgz#1b2c279a6eece380a12168b92485265b35b1effb" @@ -6218,6 +6284,11 @@ lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.3, lodash@^4.17.4, resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== +lodash@^4.17.15: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" @@ -6274,7 +6345,7 @@ lower-case@^1.1.1: resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= -lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2: +lru-cache@^4.0.1, lru-cache@^4.1.1, lru-cache@^4.1.2, lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== @@ -6567,6 +6638,11 @@ minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" @@ -6640,13 +6716,20 @@ mixin-object@^2.0.1: for-in "^0.1.3" is-extendable "^0.1.1" -mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: +mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= dependencies: minimist "0.0.8" +mkdirp@~0.5.1: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.3.tgz#5a514b7179259287952881e94410ec5465659f8c" + integrity sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg== + dependencies: + minimist "^1.2.5" + moment@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" @@ -6919,6 +7002,14 @@ nopt@^4.0.1: abbrev "1" osenv "^0.1.4" +nopt@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== + dependencies: + abbrev "1" + osenv "^0.1.4" + normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -8022,6 +8113,15 @@ pretty-format@^24.3.0: ansi-regex "^4.0.0" ansi-styles "^3.2.0" +pretty@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pretty/-/pretty-2.0.0.tgz#adbc7960b7bbfe289a557dc5f737619a220d06a5" + integrity sha1-rbx5YLe7/iiaVX3F9zdhmiINBqU= + dependencies: + condense-newlines "^0.2.1" + extend-shallow "^2.0.1" + js-beautify "^1.6.12" + printj@~1.1.0, printj@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" @@ -8065,6 +8165,11 @@ prompts@^2.0.1: kleur "^3.0.2" sisteransi "^1.0.0" +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= + proxy-addr@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" @@ -8792,7 +8897,7 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== @@ -8802,6 +8907,11 @@ semver@5.5.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== +semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -8939,6 +9049,11 @@ showdown@1.8.6: dependencies: yargs "^10.0.3" +sigmund@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"