commit: 887fac5addc2416504593cb62a52a3b08f3638e2
parent af734afe3694ff3c77d4457b93b02309c4f55d6d
Author: Henry Jameson <me@hjkos.com>
Date: Tue, 30 Aug 2022 23:54:16 +0300
Merge remote-tracking branch 'origin/develop' into scrolltotop
* origin/develop: (59 commits)
a11y
Use dedicated indicator for non-ascii domain names
add a favorites "timeline" shortcut
refactor navigation-entry and use them in other nav items
Update dependency sinon-chai to v3
Update dependency semver to v7
Update dependency vue-router to v4.1.5
Update dependency eslint to v8.23.0
Update dependency vue-template-compiler to v2.7.10
Update dependency @vue/babel-helper-vue-jsx-merge-props to v1.4.0
Update dependency eslint-plugin-promise to v6.0.1
fix lists edit page
change ugly checkbox to a list element that doesn't look too much out of place
a11y
squeeze/stretch pinned items as long as there's enough space for it, hide items that won't fitc
Remove isparta
lint
fix being unable to edit timeline pins on mobile
aria
fix mobile side drawer causing issues
...
Diffstat:
80 files changed, 1971 insertions(+), 1305 deletions(-)
diff --git a/package.json b/package.json
@@ -43,19 +43,19 @@
"utf8": "3.0.0",
"vue": "3.2.37",
"vue-i18n": "9.2.2",
- "vue-router": "4.1.3",
- "vue-template-compiler": "2.7.9",
+ "vue-router": "4.1.5",
+ "vue-template-compiler": "2.7.10",
"vuex": "4.0.2"
},
"devDependencies": {
- "@babel/core": "7.18.10",
+ "@babel/core": "7.18.13",
"@babel/eslint-parser": "7.18.9",
"@babel/plugin-transform-runtime": "7.18.10",
"@babel/preset-env": "7.18.10",
"@babel/register": "7.18.9",
"@intlify/vue-i18n-loader": "5.0.0",
"@ungap/event-target": "0.2.3",
- "@vue/babel-helper-vue-jsx-merge-props": "1.2.1",
+ "@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
"@vue/babel-plugin-jsx": "1.1.1",
"@vue/compiler-sfc": "3.2.37",
"@vue/test-utils": "2.0.2",
@@ -71,12 +71,12 @@
"css-loader": "6.7.1",
"css-minimizer-webpack-plugin": "4.0.0",
"custom-event-polyfill": "1.0.7",
- "eslint": "8.22.0",
+ "eslint": "8.23.0",
"eslint-config-standard": "17.0.0",
"eslint-formatter-friendly": "7.0.0",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-n": "15.2.5",
- "eslint-plugin-promise": "6.0.0",
+ "eslint-plugin-promise": "6.0.1",
"eslint-plugin-vue": "9.4.0",
"eslint-webpack-plugin": "3.2.0",
"eventsource-polyfill": "0.9.6",
@@ -85,7 +85,6 @@
"html-webpack-plugin": "5.5.0",
"http-proxy-middleware": "2.0.6",
"iso-639-1": "2.1.15",
- "isparta-loader": "2.0.0",
"json-loader": "0.5.7",
"karma": "6.4.0",
"karma-coverage": "2.2.0",
@@ -108,11 +107,11 @@
"sass": "1.54.5",
"sass-loader": "13.0.2",
"selenium-server": "2.53.1",
- "semver": "5.7.1",
+ "semver": "7.3.7",
"serviceworker-webpack5-plugin": "2.0.0",
"shelljs": "0.8.5",
"sinon": "2.4.1",
- "sinon-chai": "2.14.0",
+ "sinon-chai": "3.7.0",
"stylelint": "13.13.1",
"stylelint-config-standard": "20.0.0",
"stylelint-rscss": "0.4.0",
diff --git a/src/App.js b/src/App.js
@@ -92,8 +92,12 @@ export default {
isChats () {
return this.$route.name === 'chat' || this.$route.name === 'chats'
},
+ isListEdit () {
+ return this.$route.name === 'lists-edit'
+ },
newPostButtonShown () {
if (this.isChats) return false
+ if (this.isListEdit) return false
return this.$store.getters.mergedConfig.alwaysShowNewPostButton || this.layoutType === 'mobile'
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
diff --git a/src/App.scss b/src/App.scss
@@ -117,12 +117,28 @@ h4 {
margin: 0;
}
+.iconLetter {
+ display: inline-block;
+ text-align: center;
+ font-weight: 1000;
+}
+
i[class*=icon-],
-.svg-inline--fa {
+.svg-inline--fa,
+.iconLetter {
color: $fallback--icon;
color: var(--icon, $fallback--icon);
}
+.button-unstyled:hover,
+a:hover {
+ > i[class*=icon-],
+ > .svg-inline--fa,
+ > .iconLetter {
+ color: var(--text);
+ }
+}
+
nav {
z-index: var(--ZI_navbar);
color: var(--topBarText);
@@ -765,17 +781,23 @@ option {
}
.fa-scale-110 {
- &.svg-inline--fa {
+ &.svg-inline--fa,
+ &.iconLetter {
font-size: 1.1em;
}
}
.fa-old-padding {
+ &.iconLetter,
&.svg-inline--fa, &-layer {
padding: 0 0.3em;
}
}
+.veryfaint {
+ opacity: 0.25;
+}
+
.login-hint {
text-align: center;
diff --git a/src/App.vue b/src/App.vue
@@ -36,7 +36,7 @@
<div
id="main-scroller"
class="column main"
- :class="{ '-full-height': isChats }"
+ :class="{ '-full-height': isChats || isListEdit }"
>
<div
v-if="!currentUser"
diff --git a/src/boot/routes.js b/src/boot/routes.js
@@ -23,6 +23,7 @@ import RemoteUserResolver from 'components/remote_user_resolver/remote_user_reso
import Lists from 'components/lists/lists.vue'
import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
import ListsEdit from 'components/lists_edit/lists_edit.vue'
+import NavPanel from 'src/components/nav_panel/nav_panel.vue'
export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => {
@@ -79,7 +80,9 @@ export default (store) => {
{ name: 'legacy-user-profile', path: '/:name', component: UserProfile },
{ name: 'lists', path: '/lists', component: Lists },
{ name: 'lists-timeline', path: '/lists/:id', component: ListsTimeline },
- { name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit }
+ { name: 'lists-edit', path: '/lists/:id/edit', component: ListsEdit },
+ { name: 'lists-new', path: '/lists/new', component: ListsEdit },
+ { name: 'edit-navigation', path: '/nav-edit', component: NavPanel, props: () => ({ forceExpand: true, forceEditMode: true }), beforeEnter: validateAuthenticatedRoute }
]
if (store.state.instance.pleromaChatMessagesAvailable) {
diff --git a/src/components/account_actions/account_actions.js b/src/components/account_actions/account_actions.js
@@ -1,6 +1,7 @@
import { mapState } from 'vuex'
import ProgressButton from '../progress_button/progress_button.vue'
import Popover from '../popover/popover.vue'
+import UserListMenu from 'src/components/user_list_menu/user_list_menu.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisV
@@ -19,7 +20,8 @@ const AccountActions = {
},
components: {
ProgressButton,
- Popover
+ Popover,
+ UserListMenu
},
methods: {
showRepeats () {
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
@@ -28,6 +28,7 @@
class="dropdown-divider"
/>
</template>
+ <UserListMenu :user="user" />
<button
v-if="relationship.blocking"
class="btn button-default btn-block dropdown-item"
diff --git a/src/components/basic_user_card/basic_user_card.js b/src/components/basic_user_card/basic_user_card.js
@@ -1,5 +1,6 @@
import UserPopover from '../user_popover/user_popover.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
+import UserLink from '../user_link/user_link.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@@ -10,7 +11,8 @@ const BasicUserCard = {
components: {
UserPopover,
UserAvatar,
- RichContent
+ RichContent,
+ UserLink
},
methods: {
userProfileLink (user) {
diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
@@ -30,12 +30,10 @@
/>
</div>
<div>
- <router-link
+ <user-link
class="basic-user-card-screen-name"
- :to="userProfileLink(user)"
- >
- @{{ user.screen_name_ui }}
- </router-link>
+ :user="user"
+ />
</div>
<slot />
</div>
diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss
@@ -137,4 +137,8 @@
text-align: right;
}
}
+
+ .spacer {
+ width: 1em;
+ }
}
diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue
@@ -61,6 +61,7 @@
:title="$t('nav.administration')"
/>
</a>
+ <span class="spacer" />
<button
v-if="currentUser"
class="button-unstyled nav-icon"
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
@@ -1,5 +1,6 @@
import Completion from '../../services/completion/completion.js'
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
import { take } from 'lodash'
import { findOffset } from '../../services/offset_finder/offset_finder.service.js'
@@ -120,7 +121,8 @@ const EmojiInput = {
}
},
components: {
- EmojiPicker
+ EmojiPicker,
+ UnicodeDomainIndicator
},
computed: {
padEmoji () {
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
@@ -50,7 +50,21 @@
<span v-else>{{ suggestion.replacement }}</span>
</span>
<div class="label">
- <span class="displayText">{{ suggestion.displayText }}</span>
+ <span
+ v-if="suggestion.user"
+ class="displayText"
+ >
+ {{ suggestion.displayText }}<UnicodeDomainIndicator
+ :user="suggestion.user"
+ :at="false"
+ />
+ </span>
+ <span
+ v-if="!suggestion.user"
+ class="displayText"
+ >
+ {{ suggestion.displayText }}
+ </span>
<span class="detailText">{{ suggestion.detailText }}</span>
</div>
</div>
diff --git a/src/components/emoji_input/suggestor.js b/src/components/emoji_input/suggestor.js
@@ -116,11 +116,12 @@ export const suggestUsers = ({ dispatch, state }) => {
return diff + nameAlphabetically + screenNameAlphabetically
/* eslint-disable camelcase */
- }).map(({ screen_name, screen_name_ui, name, profile_image_url_original }) => ({
- displayText: screen_name_ui,
- detailText: name,
- imageUrl: profile_image_url_original,
- replacement: '@' + screen_name + ' '
+ }).map((user) => ({
+ user,
+ displayText: user.screen_name_ui,
+ detailText: user.name,
+ imageUrl: user.profile_image_url_original,
+ replacement: '@' + user.screen_name + ' '
}))
/* eslint-enable camelcase */
diff --git a/src/components/lists/lists.js b/src/components/lists/lists.js
@@ -1,5 +1,4 @@
import ListsCard from '../lists_card/lists_card.vue'
-import ListsNew from '../lists_new/lists_new.vue'
const Lists = {
data () {
@@ -8,11 +7,7 @@ const Lists = {
}
},
components: {
- ListsCard,
- ListsNew
- },
- created () {
- this.$store.dispatch('startFetchingLists')
+ ListsCard
},
computed: {
lists () {
diff --git a/src/components/lists/lists.vue b/src/components/lists/lists.vue
@@ -1,21 +1,15 @@
<template>
- <div v-if="isNew">
- <ListsNew @cancel="cancelNewList" />
- </div>
- <div
- v-else
- class="settings panel panel-default"
- >
+ <div class="Lists panel panel-default">
<div class="panel-heading">
<div class="title">
{{ $t('lists.lists') }}
</div>
- <button
- class="button-default"
- @click="newList"
+ <router-link
+ :to="{ name: 'lists-new' }"
+ class="button-default btn new-list-button"
>
{{ $t("lists.new") }}
- </button>
+ </router-link>
</div>
<div class="panel-body">
<ListsCard
@@ -29,3 +23,11 @@
</template>
<script src="./lists.js"></script>
+
+<style lang="scss">
+.Lists {
+ .new-list-button {
+ padding: 0 0.5em;
+ }
+}
+</style>
diff --git a/src/components/lists_edit/lists_edit.js b/src/components/lists_edit/lists_edit.js
@@ -1,7 +1,9 @@
import { mapState, mapGetters } from 'vuex'
import BasicUserCard from '../basic_user_card/basic_user_card.vue'
import ListsUserSearch from '../lists_user_search/lists_user_search.vue'
+import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSearch,
@@ -17,22 +19,33 @@ const ListsNew = {
components: {
BasicUserCard,
UserAvatar,
- ListsUserSearch
+ ListsUserSearch,
+ TabSwitcher,
+ PanelLoading
},
data () {
return {
title: '',
- userIds: [],
- selectedUserIds: []
+ titleDraft: '',
+ membersUserIds: [],
+ removedUserIds: new Set([]), // users we added for members, to undo
+ searchUserIds: [],
+ addedUserIds: new Set([]), // users we added from search, to undo
+ searchLoading: false,
+ reallyDelete: false
}
},
created () {
- this.$store.dispatch('fetchList', { id: this.id })
- .then(() => { this.title = this.findListTitle(this.id) })
- this.$store.dispatch('fetchListAccounts', { id: this.id })
+ if (!this.id) return
+ this.$store.dispatch('fetchList', { listId: this.id })
.then(() => {
- this.selectedUserIds = this.findListAccounts(this.id)
- this.selectedUserIds.forEach(userId => {
+ this.title = this.findListTitle(this.id)
+ this.titleDraft = this.title
+ })
+ this.$store.dispatch('fetchListAccounts', { listId: this.id })
+ .then(() => {
+ this.membersUserIds = this.findListAccounts(this.id)
+ this.membersUserIds.forEach(userId => {
this.$store.dispatch('fetchUserIfMissing', userId)
})
})
@@ -41,11 +54,12 @@ const ListsNew = {
id () {
return this.$route.params.id
},
- users () {
- return this.userIds.map(userId => this.findUser(userId))
+ membersUsers () {
+ return [...this.membersUserIds, ...this.addedUserIds]
+ .map(userId => this.findUser(userId)).filter(user => user)
},
- selectedUsers () {
- return this.selectedUserIds.map(userId => this.findUser(userId)).filter(user => user)
+ searchUsers () {
+ return this.searchUserIds.map(userId => this.findUser(userId)).filter(user => user)
},
...mapState({
currentUser: state => state.users.currentUser
@@ -56,33 +70,73 @@ const ListsNew = {
onInput () {
this.search(this.query)
},
- selectUser (user) {
- if (this.selectedUserIds.includes(user.id)) {
- this.removeUser(user.id)
+ toggleRemoveMember (user) {
+ if (this.removedUserIds.has(user.id)) {
+ this.id && this.addUser(user)
+ this.removedUserIds.delete(user.id)
} else {
- this.addUser(user)
+ this.id && this.removeUser(user.id)
+ this.removedUserIds.add(user.id)
}
},
- isSelected (user) {
- return this.selectedUserIds.includes(user.id)
+ toggleAddFromSearch (user) {
+ if (this.addedUserIds.has(user.id)) {
+ this.id && this.removeUser(user.id)
+ this.addedUserIds.delete(user.id)
+ } else {
+ this.id && this.addUser(user)
+ this.addedUserIds.add(user.id)
+ }
+ },
+ isRemoved (user) {
+ return this.removedUserIds.has(user.id)
+ },
+ isAdded (user) {
+ return this.addedUserIds.has(user.id)
},
addUser (user) {
- this.selectedUserIds.push(user.id)
+ this.$store.dispatch('addListAccount', { accountId: this.user.id, listId: this.id })
},
removeUser (userId) {
- this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId)
+ this.$store.dispatch('removeListAccount', { accountId: this.user.id, listId: this.id })
},
- onResults (results) {
- this.userIds = results
+ onSearchLoading (results) {
+ this.searchLoading = true
},
- updateList () {
- this.$store.dispatch('setList', { id: this.id, title: this.title })
- this.$store.dispatch('setListAccounts', { id: this.id, accountIds: this.selectedUserIds })
-
- this.$router.push({ name: 'lists-timeline', params: { id: this.id } })
+ onSearchLoadingDone (results) {
+ this.searchLoading = false
+ },
+ onSearchResults (results) {
+ this.searchLoading = false
+ this.searchUserIds = results
+ },
+ updateListTitle () {
+ this.$store.dispatch('setList', { listId: this.id, title: this.titleDraft })
+ .then(() => {
+ this.title = this.findListTitle(this.id)
+ })
+ },
+ createList () {
+ this.$store.dispatch('createList', { title: this.titleDraft })
+ .then((list) => {
+ return this
+ .$store
+ .dispatch('setListAccounts', { listId: list.id, accountIds: [...this.addedUserIds] })
+ .then(() => list.id)
+ })
+ .then((listId) => {
+ this.$router.push({ name: 'lists-timeline', params: { id: listId } })
+ })
+ .catch((e) => {
+ this.$store.dispatch('pushGlobalNotice', {
+ messageKey: 'lists.error',
+ messageArgs: [e.message],
+ level: 'error'
+ })
+ })
},
deleteList () {
- this.$store.dispatch('deleteList', { id: this.id })
+ this.$store.dispatch('deleteList', { listId: this.id })
this.$router.push({ name: 'lists' })
}
}
diff --git a/src/components/lists_edit/lists_edit.vue b/src/components/lists_edit/lists_edit.vue
@@ -1,8 +1,8 @@
<template>
- <div class="panel-default panel list-edit">
+ <div class="panel-default panel ListEdit">
<div
ref="header"
- class="panel-heading"
+ class="panel-heading list-edit-heading"
>
<button
class="button-unstyled go-back-button"
@@ -13,54 +13,151 @@
icon="chevron-left"
/>
</button>
+ <div class="title">
+ <i18n-t
+ v-if="id"
+ keypath="lists.editing_list"
+ >
+ <template #listTitle>
+ {{ title }}
+ </template>
+ </i18n-t>
+ <i18n-t
+ v-else
+ keypath="lists.creating_list"
+ />
+ </div>
</div>
- <div class="input-wrap">
- <input
- ref="title"
- v-model="title"
- :placeholder="$t('lists.title')"
+ <div class="panel-body">
+ <div class="input-wrap">
+ <label for="list-edit-title">{{ $t('lists.title') }}</label>
+ {{ ' ' }}
+ <input
+ id="list-edit-title"
+ ref="title"
+ v-model="titleDraft"
+ >
+ <button
+ v-if="id"
+ class="btn button-default follow-button"
+ @click="updateListTitle"
+ >
+ {{ $t('lists.update_title') }}
+ </button>
+ </div>
+ <tab-switcher
+ class="list-member-management"
+ :scrollable-tabs="true"
>
+ <div
+ v-if="id || addedUserIds.size > 0"
+ :label="$t('lists.manage_members')"
+ class="members-list"
+ >
+ <div class="users-list">
+ <div
+ v-for="user in membersUsers"
+ :key="user.id"
+ class="member"
+ >
+ <BasicUserCard
+ :user="user"
+ >
+ <button
+ class="btn button-default follow-button"
+ @click="toggleRemoveMember(user)"
+ >
+ {{ isRemoved(user) ? $t('general.undo') : $t('lists.remove_from_list') }}
+ </button>
+ </BasicUserCard>
+ </div>
+ </div>
+ </div>
+
+ <div
+ class="search-list"
+ :label="$t('lists.add_members')"
+ >
+ <ListsUserSearch
+ @results="onSearchResults"
+ @loading="onSearchLoading"
+ @loadingDone="onSearchLoadingDone"
+ />
+ <div
+ v-if="searchLoading"
+ class="loading"
+ >
+ <PanelLoading />
+ </div>
+ <div
+ v-else
+ class="users-list"
+ >
+ <div
+ v-for="user in searchUsers"
+ :key="user.id"
+ class="member"
+ >
+ <BasicUserCard
+ :user="user"
+ >
+ <span
+ v-if="membersUserIds.includes(user.id)"
+ >
+ {{ $t('lists.is_in_list') }}
+ </span>
+ <button
+ v-if="!membersUserIds.includes(user.id)"
+ class="btn button-default follow-button"
+ @click="toggleAddFromSearch(user)"
+ >
+ {{ isAdded(user) ? $t('general.undo') : $t('lists.add_to_list') }}
+ </button>
+ <button
+ v-else
+ class="btn button-default follow-button"
+ @click="toggleRemoveMember(user)"
+ >
+ {{ isRemoved(user) ? $t('general.undo') : $t('lists.remove_from_list') }}
+ </button>
+ </BasicUserCard>
+ </div>
+ </div>
+ </div>
+ </tab-switcher>
</div>
- <div class="member-list">
- <div
- v-for="user in selectedUsers"
- :key="user.id"
- class="member"
+ <div class="panel-footer">
+ <span class="spacer" />
+ <button
+ v-if="!id"
+ class="btn button-default footer-button"
+ @click="createList"
>
- <BasicUserCard
- :user="user"
- :class="isSelected(user) ? 'selected' : ''"
- @click.capture.prevent="selectUser(user)"
- />
- </div>
- </div>
- <ListsUserSearch @results="onResults" />
- <div class="member-list">
- <div
- v-for="user in users"
- :key="user.id"
- class="member"
+ {{ $t('lists.create') }}
+ </button>
+ <button
+ v-else-if="!reallyDelete"
+ class="btn button-default footer-button"
+ @click="reallyDelete = true"
>
- <BasicUserCard
- :user="user"
- :class="isSelected(user) ? 'selected' : ''"
- @click.capture.prevent="selectUser(user)"
- />
- </div>
+ {{ $t('lists.delete') }}
+ </button>
+ <template v-else>
+ {{ $t('lists.really_delete') }}
+ <button
+ class="btn button-default footer-button"
+ @click="deleteList"
+ >
+ {{ $t('general.yes') }}
+ </button>
+ <button
+ class="btn button-default footer-button"
+ @click="reallyDelete = false"
+ >
+ {{ $t('general.no') }}
+ </button>
+ </template>
</div>
- <button
- :disabled="title && title.length === 0"
- class="btn button-default"
- @click="updateList"
- >
- {{ $t('lists.save') }}
- </button>
- <button
- class="btn button-default"
- @click="deleteList"
- >
- {{ $t('lists.delete') }}
- </button>
</div>
</template>
@@ -69,28 +166,43 @@
<style lang="scss">
@import '../../_variables.scss';
-.list-edit {
- .input-wrap {
+.ListEdit {
+ --panel-body-padding: 0.5em;
+
+ height: calc(100vh - var(--navbar-height));
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+
+ .list-edit-heading {
+ grid-template-columns: auto minmax(50%, 1fr);
+ }
+
+ .panel-body {
display: flex;
- margin: 0.7em 0.5em 0.7em 0.5em;
+ flex: 1;
+ flex-direction: column;
+ overflow: hidden;
+ }
- input {
- width: 100%;
- }
+ .list-member-management {
+ flex: 1 0 auto;
}
.search-icon {
margin-right: 0.3em;
}
- .member-list {
+ .users-list {
padding-bottom: 0.7rem;
+ overflow-y: auto;
}
- .basic-user-card:hover,
- .basic-user-card.selected {
- cursor: pointer;
- background-color: var(--selectedPost, $fallback--lightBg);
+ & .search-list,
+ & .members-list {
+ overflow: hidden;
+ flex-direction: column;
+ min-height: 0;
}
.go-back-button {
@@ -102,7 +214,15 @@
}
.btn {
- margin: 0.5em;
+ margin: 0 0.5em;
+ }
+
+ .panel-footer {
+ grid-template-columns: minmax(10%, 1fr);
+
+ .footer-button {
+ min-width: 9em;
+ }
}
}
</style>
diff --git a/src/components/lists_menu/lists_menu_content.js b/src/components/lists_menu/lists_menu_content.js
@@ -1,28 +1,17 @@
import { mapState } from 'vuex'
-import { library } from '@fortawesome/fontawesome-svg-core'
-import {
- faUsers,
- faGlobe,
- faBookmark,
- faEnvelope,
- faHome
-} from '@fortawesome/free-solid-svg-icons'
+import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
+import { getListEntries } from 'src/components/navigation/filter.js'
-library.add(
- faUsers,
- faGlobe,
- faBookmark,
- faEnvelope,
- faHome
-)
-
-const ListsMenuContent = {
- created () {
- this.$store.dispatch('startFetchingLists')
+export const ListsMenuContent = {
+ props: [
+ 'showPin'
+ ],
+ components: {
+ NavigationEntry
},
computed: {
...mapState({
- lists: state => state.lists.allLists,
+ lists: getListEntries,
currentUser: state => state.users.currentUser,
privateMode: state => state.instance.private,
federating: state => state.instance.federating
diff --git a/src/components/lists_menu/lists_menu_content.vue b/src/components/lists_menu/lists_menu_content.vue
@@ -1,16 +1,11 @@
<template>
<ul>
- <li
- v-for="list in lists.slice().reverse()"
- :key="list.id"
- >
- <router-link
- class="menu-item"
- :to="{ name: 'lists-timeline', params: { id: list.id } }"
- >
- {{ list.title }}
- </router-link>
- </li>
+ <NavigationEntry
+ v-for="item in lists"
+ :key="item.name"
+ :show-pin="showPin"
+ :item="item"
+ />
</ul>
</template>
diff --git a/src/components/lists_new/lists_new.js b/src/components/lists_new/lists_new.js
@@ -1,79 +0,0 @@
-import { mapState, mapGetters } from 'vuex'
-import BasicUserCard from '../basic_user_card/basic_user_card.vue'
-import UserAvatar from '../user_avatar/user_avatar.vue'
-import ListsUserSearch from '../lists_user_search/lists_user_search.vue'
-import { library } from '@fortawesome/fontawesome-svg-core'
-import {
- faSearch,
- faChevronLeft
-} from '@fortawesome/free-solid-svg-icons'
-
-library.add(
- faSearch,
- faChevronLeft
-)
-
-const ListsNew = {
- components: {
- BasicUserCard,
- UserAvatar,
- ListsUserSearch
- },
- data () {
- return {
- title: '',
- userIds: [],
- selectedUserIds: []
- }
- },
- computed: {
- users () {
- return this.userIds.map(userId => this.findUser(userId))
- },
- selectedUsers () {
- return this.selectedUserIds.map(userId => this.findUser(userId))
- },
- ...mapState({
- currentUser: state => state.users.currentUser
- }),
- ...mapGetters(['findUser'])
- },
- methods: {
- goBack () {
- this.$emit('cancel')
- },
- onInput () {
- this.search(this.query)
- },
- selectUser (user) {
- if (this.selectedUserIds.includes(user.id)) {
- this.removeUser(user.id)
- } else {
- this.addUser(user)
- }
- },
- isSelected (user) {
- return this.selectedUserIds.includes(user.id)
- },
- addUser (user) {
- this.selectedUserIds.push(user.id)
- },
- removeUser (userId) {
- this.selectedUserIds = this.selectedUserIds.filter(id => id !== userId)
- },
- onResults (results) {
- this.userIds = results
- },
- createList () {
- // the API has two different endpoints for "creating a list with a name"
- // and "updating the accounts on the list".
- this.$store.dispatch('createList', { title: this.title })
- .then((list) => {
- this.$store.dispatch('setListAccounts', { id: list.id, accountIds: this.selectedUserIds })
- this.$router.push({ name: 'lists-timeline', params: { id: list.id } })
- })
- }
- }
-}
-
-export default ListsNew
diff --git a/src/components/lists_new/lists_new.vue b/src/components/lists_new/lists_new.vue
@@ -1,95 +0,0 @@
-<template>
- <div class="panel-default panel list-new">
- <div
- ref="header"
- class="panel-heading"
- >
- <button
- class="button-unstyled go-back-button"
- @click="goBack"
- >
- <FAIcon
- size="lg"
- icon="chevron-left"
- />
- </button>
- </div>
- <div class="input-wrap">
- <input
- ref="title"
- v-model="title"
- :placeholder="$t('lists.title')"
- >
- </div>
-
- <div class="member-list">
- <div
- v-for="user in selectedUsers"
- :key="user.id"
- class="member"
- >
- <BasicUserCard
- :user="user"
- :class="isSelected(user) ? 'selected' : ''"
- @click.capture.prevent="selectUser(user)"
- />
- </div>
- </div>
- <ListsUserSearch
- @results="onResults"
- />
- <div
- v-for="user in users"
- :key="user.id"
- class="member"
- >
- <BasicUserCard
- :user="user"
- :class="isSelected(user) ? 'selected' : ''"
- @click.capture.prevent="selectUser(user)"
- />
- </div>
-
- <button
- :disabled="title && title.length === 0"
- class="btn button-default"
- @click="createList"
- >
- {{ $t('lists.create') }}
- </button>
- </div>
-</template>
-
-<script src="./lists_new.js"></script>
-
-<style lang="scss">
-@import '../../_variables.scss';
-
-.list-new {
- .search-icon {
- margin-right: 0.3em;
- }
-
- .member-list {
- padding-bottom: 0.7rem;
- }
-
- .basic-user-card:hover,
- .basic-user-card.selected {
- cursor: pointer;
- background-color: var(--selectedPost, $fallback--lightBg);
- }
-
- .go-back-button {
- text-align: center;
- line-height: 1;
- height: 100%;
- align-self: start;
- width: var(--__panel-heading-height-inner);
- }
-
- .btn {
- margin: 0.5em;
- }
-}
-</style>
diff --git a/src/components/lists_timeline/lists_timeline.js b/src/components/lists_timeline/lists_timeline.js
@@ -17,14 +17,14 @@ const ListsTimeline = {
this.listId = route.params.id
this.$store.dispatch('stopFetchingTimeline', 'list')
this.$store.commit('clearTimeline', { timeline: 'list' })
- this.$store.dispatch('fetchList', { id: this.listId })
+ this.$store.dispatch('fetchList', { listId: this.listId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
}
}
},
created () {
this.listId = this.$route.params.id
- this.$store.dispatch('fetchList', { id: this.listId })
+ this.$store.dispatch('fetchList', { listId: this.listId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
},
unmounted () {
diff --git a/src/components/lists_user_search/lists_user_search.js b/src/components/lists_user_search/lists_user_search.js
@@ -15,6 +15,7 @@ const ListsUserSearch = {
components: {
Checkbox
},
+ emits: ['loading', 'loadingDone', 'results'],
data () {
return {
loading: false,
@@ -33,12 +34,16 @@ const ListsUserSearch = {
}
this.loading = true
+ this.$emit('loading')
this.userIds = []
this.$store.dispatch('search', { q: query, resolve: true, type: 'accounts', following: this.followingOnly })
.then(data => {
- this.loading = false
this.$emit('results', data.accounts.map(a => a.id))
})
+ .finally(() => {
+ this.loading = false
+ this.$emit('loadingDone')
+ })
}
}
}
diff --git a/src/components/lists_user_search/lists_user_search.vue b/src/components/lists_user_search/lists_user_search.vue
@@ -1,5 +1,5 @@
<template>
- <div>
+ <div class="ListsUserSearch">
<div class="input-wrap">
<div class="input-search">
<FAIcon
@@ -29,17 +29,19 @@
<style lang="scss">
@import '../../_variables.scss';
-.input-wrap {
- display: flex;
- margin: 0.7em 0.5em 0.7em 0.5em;
+.ListsUserSearch {
+ .input-wrap {
+ display: flex;
+ margin: 0.7em 0.5em 0.7em 0.5em;
- input {
- width: 100%;
+ input {
+ width: 100%;
+ }
}
-}
-.search-icon {
- margin-right: 0.3em;
+ .search-icon {
+ margin-right: 0.3em;
+ }
}
</style>
diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js
@@ -2,6 +2,7 @@ import generateProfileLink from 'src/services/user_profile_link_generator/user_p
import { mapGetters, mapState } from 'vuex'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter.js'
import UserAvatar from '../user_avatar/user_avatar.vue'
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
import { defineAsyncComponent } from 'vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -16,6 +17,7 @@ const MentionLink = {
name: 'MentionLink',
components: {
UserAvatar,
+ UnicodeDomainIndicator,
UserPopover: defineAsyncComponent(() => import('../user_popover/user_popover.vue'))
},
props: {
diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue
@@ -47,6 +47,9 @@
class="serverName"
:class="{ '-faded': shouldFadeDomain }"
v-html="'@' + serverName"
+ /><UnicodeDomainIndicator
+ v-if="shouldShowFullUserName"
+ :user="user"
/>
</span>
<span
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
@@ -2,6 +2,7 @@ import SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
+import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -19,7 +20,8 @@ library.add(
const MobileNav = {
components: {
SideDrawer,
- Notifications
+ Notifications,
+ NavigationPins
},
data: () => ({
notificationsCloseGesture: undefined,
@@ -47,7 +49,10 @@ const MobileNav = {
isChat () {
return this.$route.name === 'chat'
},
- ...mapGetters(['unreadChatCount'])
+ ...mapGetters(['unreadChatCount']),
+ chatsPinned () {
+ return new Set(this.$store.state.serverSideStorage.prefsStorage.collections.pinnedNavItems).has('chats')
+ }
},
methods: {
toggleMobileSidebar () {
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
@@ -17,20 +17,12 @@
icon="bars"
/>
<div
- v-if="unreadChatCount"
+ v-if="unreadChatCount && !chatsPinned"
class="alert-dot"
/>
</button>
- <router-link
- v-if="!hideSitename"
- class="site-name"
- :to="{ name: 'root' }"
- active-class="home"
- >
- {{ sitename }}
- </router-link>
- </div>
- <div class="item right">
+ <NavigationPins class="pins" />
+ </div> <div class="item right">
<button
v-if="currentUser"
class="button-unstyled mobile-nav-button"
@@ -94,6 +86,7 @@
grid-template-columns: 2fr auto;
width: 100%;
box-sizing: border-box;
+
a {
color: var(--topBarLink, $fallback--link);
}
@@ -178,13 +171,20 @@
}
}
+ .pins {
+ flex: 1;
+
+ .pinned-item {
+ flex-grow: 1;
+ }
+ }
+
.mobile-notifications {
margin-top: 50px;
width: 100vw;
height: calc(100vh - var(--navbar-height));
overflow-x: hidden;
overflow-y: scroll;
-
color: $fallback--text;
color: var(--text, $fallback--text);
background-color: $fallback--bg;
@@ -194,14 +194,17 @@
padding: 0;
border-radius: 0;
box-shadow: none;
+
.panel {
border-radius: 0;
margin: 0;
box-shadow: none;
}
- .panel:after {
+
+ .panel::after {
border-radius: 0;
}
+
.panel .panel-heading {
border-radius: 0;
box-shadow: none;
diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.js b/src/components/mobile_post_status_button/mobile_post_status_button.js
@@ -10,7 +10,8 @@ library.add(
const HIDDEN_FOR_PAGES = new Set([
'chats',
- 'chat'
+ 'chat',
+ 'lists-edit'
])
const MobilePostStatusButton = {
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
@@ -1,6 +1,10 @@
-import TimelineMenuContent from '../timeline_menu/timeline_menu_content.vue'
-import ListsMenuContent from '../lists_menu/lists_menu_content.vue'
+import ListsMenuContent from 'src/components/lists_menu/lists_menu_content.vue'
import { mapState, mapGetters } from 'vuex'
+import { TIMELINES, ROOT_ITEMS } from 'src/components/navigation/navigation.js'
+import { filterNavigation } from 'src/components/navigation/filter.js'
+import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
+import NavigationPins from 'src/components/navigation/navigation_pins.vue'
+import Checkbox from 'src/components/checkbox/checkbox.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -30,21 +34,23 @@ library.add(
faStream,
faList
)
-
const NavPanel = {
+ props: ['forceExpand', 'forceEditMode'],
created () {
- if (this.currentUser && this.currentUser.locked) {
- this.$store.dispatch('startFetchingFollowRequests')
- }
},
components: {
- TimelineMenuContent,
- ListsMenuContent
+ ListsMenuContent,
+ NavigationEntry,
+ NavigationPins,
+ Checkbox
},
data () {
return {
+ editMode: false,
showTimelines: false,
- showLists: false
+ showLists: false,
+ timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ ...v, name: k })),
+ rootList: Object.entries(ROOT_ITEMS).map(([k, v]) => ({ ...v, name: k }))
}
},
methods: {
@@ -53,19 +59,62 @@ const NavPanel = {
},
toggleLists () {
this.showLists = !this.showLists
+ },
+ toggleEditMode () {
+ this.editMode = !this.editMode
+ },
+ toggleCollapse () {
+ this.$store.commit('setPreference', { path: 'simple.collapseNav', value: !this.collapsed })
+ this.$store.dispatch('pushServerSideStorage')
+ },
+ isPinned (item) {
+ return this.pinnedItems.has(item)
+ },
+ togglePin (item) {
+ if (this.isPinned(item)) {
+ this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value: item })
+ } else {
+ this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value: item })
+ }
+ this.$store.dispatch('pushServerSideStorage')
}
},
computed: {
- listsNavigation () {
- return this.$store.getters.mergedConfig.listsNavigation
- },
...mapState({
currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private,
federating: state => state.instance.federating,
- pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
+ pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
+ pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems),
+ collapsed: state => state.serverSideStorage.prefsStorage.simple.collapseNav
}),
+ timelinesItems () {
+ return filterNavigation(
+ Object
+ .entries({ ...TIMELINES })
+ .map(([k, v]) => ({ ...v, name: k })),
+ {
+ hasChats: this.pleromaChatMessagesAvailable,
+ isFederating: this.federating,
+ isPrivate: this.privateMode,
+ currentUser: this.currentUser
+ }
+ )
+ },
+ rootItems () {
+ return filterNavigation(
+ Object
+ .entries({ ...ROOT_ITEMS })
+ .map(([k, v]) => ({ ...v, name: k })),
+ {
+ hasChats: this.pleromaChatMessagesAvailable,
+ isFederating: this.federating,
+ isPrivate: this.privateMode,
+ currentUser: this.currentUser
+ }
+ )
+ },
...mapGetters(['unreadChatCount'])
}
}
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
@@ -1,135 +1,99 @@
<template>
<div class="NavPanel">
<div class="panel panel-default">
- <ul>
- <li v-if="currentUser || !privateMode">
- <button
- class="button-unstyled menu-item"
- @click="toggleTimelines"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110"
- icon="stream"
- />{{ $t("nav.timelines") }}
- <FAIcon
- class="timelines-chevron"
- fixed-width
- :icon="showTimelines ? 'chevron-up' : 'chevron-down'"
- />
- </button>
- <div
- v-show="showTimelines"
- class="timelines-background"
- >
- <TimelineMenuContent class="timelines" />
- </div>
- </li>
- <li v-if="currentUser && listsNavigation">
- <button
- class="button-unstyled menu-item"
- @click="toggleLists"
- >
- <router-link
- :to="{ name: 'lists' }"
- @click.stop
- >
- <FAIcon
- fixed-width
- class="fa-scale-110"
- icon="list"
- />{{ $t("nav.lists") }}
- </router-link>
- <FAIcon
- class="timelines-chevron"
- fixed-width
- :icon="showLists ? 'chevron-up' : 'chevron-down'"
+ <div
+ v-if="!forceExpand"
+ class="panel-heading nav-panel-heading"
+ >
+ <NavigationPins :limit="6" />
+ <div class="spacer" />
+ <button
+ class="button-unstyled"
+ @click="toggleCollapse"
+ >
+ <FAIcon
+ class="timelines-chevron"
+ fixed-width
+ :icon="collapsed ? 'chevron-down' : 'chevron-up'"
+ />
+ </button>
+ </div>
+ <ul
+ v-if="!collapsed || forceExpand"
+ class="panel-body"
+ >
+ <NavigationEntry
+ v-if="currentUser || !privateMode"
+ :show-pin="false"
+ :item="{ icon: 'stream', label: 'nav.timelines' }"
+ :aria-expanded="showTimelines ? 'true' : 'false'"
+ @click="toggleTimelines"
+ >
+ <FAIcon
+ class="timelines-chevron"
+ fixed-width
+ :icon="showTimelines ? 'chevron-up' : 'chevron-down'"
+ />
+ </NavigationEntry>
+ <div
+ v-show="showTimelines"
+ class="timelines-background"
+ >
+ <div class="timelines">
+ <NavigationEntry
+ v-for="item in timelinesItems"
+ :key="item.name"
+ :show-pin="editMode || forceEditMode"
+ :item="item"
/>
- </button>
- <div
- v-show="showLists"
- class="timelines-background"
- >
- <ListsMenuContent class="timelines" />
</div>
- </li>
- <li v-if="currentUser && !listsNavigation">
+ </div>
+ <NavigationEntry
+ v-if="currentUser"
+ :show-pin="false"
+ :item="{ icon: 'list', label: 'nav.lists' }"
+ :aria-expanded="showLists ? 'true' : 'false'"
+ @click="toggleLists"
+ >
<router-link
+ :title="$t('lists.manage_lists')"
+ class="extra-button"
:to="{ name: 'lists' }"
@click.stop
>
- <button
- class="button-unstyled menu-item"
- @click="toggleLists"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110"
- icon="list"
- />{{ $t("nav.lists") }}
- </button>
- </router-link>
- </li>
- <li v-if="currentUser">
- <router-link
- class="menu-item"
- :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110"
- icon="bell"
- />{{ $t("nav.interactions") }}
- </router-link>
- </li>
- <li v-if="currentUser && pleromaChatMessagesAvailable">
- <router-link
- class="menu-item"
- :to="{ name: 'chats', params: { username: currentUser.screen_name } }"
- >
- <div
- v-if="unreadChatCount"
- class="badge badge-notification"
- >
- {{ unreadChatCount }}
- </div>
- <FAIcon
- fixed-width
- class="fa-scale-110"
- icon="comments"
- />{{ $t("nav.chats") }}
- </router-link>
- </li>
- <li v-if="currentUser && currentUser.locked">
- <router-link
- class="menu-item"
- :to="{ name: 'friend-requests' }"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110"
- icon="user-plus"
- />{{ $t("nav.friend_requests") }}
- <span
- v-if="followRequestCount > 0"
- class="badge badge-notification"
- >
- {{ followRequestCount }}
- </span>
- </router-link>
- </li>
- <li>
- <router-link
- class="menu-item"
- :to="{ name: 'about' }"
- >
<FAIcon
+ class="extra-button"
fixed-width
- class="fa-scale-110"
- icon="info-circle"
- />{{ $t("nav.about") }}
+ icon="wrench"
+ />
</router-link>
- </li>
+ <FAIcon
+ class="timelines-chevron"
+ fixed-width
+ :icon="showLists ? 'chevron-up' : 'chevron-down'"
+ />
+ </NavigationEntry>
+ <div
+ v-show="showLists"
+ class="timelines-background"
+ >
+ <ListsMenuContent
+ :show-pin="editMode || forceEditMode"
+ class="timelines"
+ />
+ </div>
+ <NavigationEntry
+ v-for="item in rootItems"
+ :key="item.name"
+ :show-pin="editMode || forceEditMode"
+ :item="item"
+ />
+ <NavigationEntry
+ v-if="!forceEditMode && currentUser"
+ :show-pin="false"
+ :item="{ label: editMode ? $t('nav.edit_finish') : $t('nav.edit_pinned'), icon: editMode ? 'check' : 'wrench' }"
+ @click="toggleEditMode"
+ />
</ul>
</div>
</div>
@@ -180,54 +144,23 @@
border: none;
}
- .menu-item {
- display: block;
- box-sizing: border-box;
- height: 3.5em;
- line-height: 3.5em;
- padding: 0 1em;
- width: 100%;
- color: $fallback--link;
- color: var(--link, $fallback--link);
-
- &:hover {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: $fallback--link;
- color: var(--selectedMenuText, $fallback--link);
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
- --icon: var(--selectedMenuIcon, $fallback--icon);
- }
-
- &.router-link-active {
- font-weight: bolder;
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: $fallback--text;
- color: var(--selectedMenuText, $fallback--text);
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
- --icon: var(--selectedMenuIcon, $fallback--icon);
-
- &:hover {
- text-decoration: underline;
- }
- }
- }
-
.timelines-chevron {
margin-left: 0.8em;
+ margin-right: 0.8em;
font-size: 1.1em;
}
+ .menu-item {
+ .timelines-chevron {
+ margin-right: 0;
+ }
+ }
+
.timelines-background {
padding: 0 0 0 0.6em;
background-color: $fallback--lightBg;
background-color: var(--selectedMenu, $fallback--lightBg);
- border-top: 1px solid;
+ border-bottom: 1px solid;
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
}
@@ -237,14 +170,9 @@
background-color: var(--bg, $fallback--bg);
}
- .fa-scale-110 {
- margin-right: 0.8em;
- }
-
- .badge {
- position: absolute;
- right: 0.6rem;
- top: 1.25em;
+ .nav-panel-heading {
+ // breaks without a unit
+ --panel-heading-height-padding: 0em;
}
}
</style>
diff --git a/src/components/navigation/filter.js b/src/components/navigation/filter.js
@@ -0,0 +1,18 @@
+export const filterNavigation = (list = [], { hasChats, isFederating, isPrivate, currentUser }) => {
+ return list.filter(({ criteria, anon, anonRoute }) => {
+ const set = new Set(criteria || [])
+ if (!isFederating && set.has('federating')) return false
+ if (isPrivate && set.has('!private')) return false
+ if (!currentUser && !(anon || anonRoute)) return false
+ if ((!currentUser || !currentUser.locked) && set.has('lockedUser')) return false
+ if (!hasChats && set.has('chats')) return false
+ return true
+ })
+}
+
+export const getListEntries = state => state.lists.allLists.map(list => ({
+ name: 'list-' + list.id,
+ routeObject: { name: 'lists-timeline', params: { id: list.id } },
+ labelRaw: list.title,
+ iconLetter: list.title[0]
+}))
diff --git a/src/components/navigation/navigation.js b/src/components/navigation/navigation.js
@@ -0,0 +1,75 @@
+export const USERNAME_ROUTES = new Set([
+ 'bookmarks',
+ 'dms',
+ 'interactions',
+ 'notifications',
+ 'chat',
+ 'chats',
+ 'user-profile'
+])
+
+export const TIMELINES = {
+ home: {
+ route: 'friends',
+ icon: 'home',
+ label: 'nav.home_timeline',
+ criteria: ['!private']
+ },
+ public: {
+ route: 'public-timeline',
+ anon: true,
+ icon: 'users',
+ label: 'nav.public_tl',
+ criteria: ['!private']
+ },
+ twkn: {
+ route: 'public-external-timeline',
+ anon: true,
+ icon: 'globe',
+ label: 'nav.twkn',
+ criteria: ['!private', 'federating']
+ },
+ bookmarks: {
+ route: 'bookmarks',
+ icon: 'bookmark',
+ label: 'nav.bookmarks'
+ },
+ favorites: {
+ routeObject: { name: 'user-profile', query: { tab: 'favorites' } },
+ icon: 'star',
+ label: 'user_card.favorites'
+ },
+ dms: {
+ route: 'dms',
+ icon: 'envelope',
+ label: 'nav.dms'
+ }
+}
+
+export const ROOT_ITEMS = {
+ interactions: {
+ route: 'interactions',
+ icon: 'bell',
+ label: 'nav.interactions'
+ },
+ chats: {
+ route: 'chats',
+ icon: 'comments',
+ label: 'nav.chats',
+ badgeGetter: 'unreadChatCount',
+ criteria: ['chats']
+ },
+ friendRequests: {
+ route: 'friend-requests',
+ icon: 'user-plus',
+ label: 'nav.friend_requests',
+ criteria: ['lockedUser'],
+ badgeGetter: 'followRequestCount'
+ },
+ about: {
+ route: 'about',
+ anon: true,
+ icon: 'info-circle',
+ label: 'nav.about'
+ }
+}
diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js
@@ -0,0 +1,47 @@
+import { mapState } from 'vuex'
+import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faThumbtack } from '@fortawesome/free-solid-svg-icons'
+
+library.add(faThumbtack)
+
+const NavigationEntry = {
+ props: ['item', 'showPin'],
+ methods: {
+ isPinned (value) {
+ return this.pinnedItems.has(value)
+ },
+ togglePin (value) {
+ if (this.isPinned(value)) {
+ this.$store.commit('removeCollectionPreference', { path: 'collections.pinnedNavItems', value })
+ } else {
+ this.$store.commit('addCollectionPreference', { path: 'collections.pinnedNavItems', value })
+ }
+ this.$store.dispatch('pushServerSideStorage')
+ }
+ },
+ computed: {
+ routeTo () {
+ if (!this.item.route && !this.item.routeObject) return null
+ let route
+ if (this.item.routeObject) {
+ route = this.item.routeObject
+ } else {
+ route = { name: (this.item.anon || this.currentUser) ? this.item.route : this.item.anonRoute }
+ }
+ if (USERNAME_ROUTES.has(route.name)) {
+ route.params = { username: this.currentUser.screen_name, name: this.currentUser.screen_name }
+ }
+ return route
+ },
+ getters () {
+ return this.$store.getters
+ },
+ ...mapState({
+ currentUser: state => state.users.currentUser,
+ pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
+ })
+ }
+}
+
+export default NavigationEntry
diff --git a/src/components/navigation/navigation_entry.vue b/src/components/navigation/navigation_entry.vue
@@ -0,0 +1,120 @@
+<template>
+ <li class="NavigationEntry">
+ <component
+ :is="routeTo ? 'router-link' : 'button'"
+ class="menu-item button-unstyled"
+ :to="routeTo"
+ >
+ <span>
+ <FAIcon
+ v-if="item.icon"
+ fixed-width
+ class="fa-scale-110 menu-icon"
+ :icon="item.icon"
+ />
+ </span>
+ <span
+ v-if="item.iconLetter"
+ class="icon iconLetter fa-scale-110 menu-icon"
+ >{{ item.iconLetter }}
+ </span>
+ <span class="label">
+ {{ item.labelRaw || $t(item.label) }}
+ </span>
+ <slot />
+ <div
+ v-if="item.badgeGetter && getters[item.badgeGetter]"
+ class="badge badge-notification"
+ >
+ {{ getters[item.badgeGetter] }}
+ </div>
+ <button
+ v-if="showPin && currentUser"
+ type="button"
+ class="button-unstyled extra-button"
+ :title="$t(isPinned ? 'general.unpin' : 'general.pin' )"
+ :aria-pressed="!!isPinned"
+ @click.stop.prevent="togglePin(item.name)"
+ >
+ <FAIcon
+ v-if="showPin && currentUser"
+ fixed-width
+ class="fa-scale-110"
+ :class="{ 'veryfaint': !isPinned(item.name) }"
+ :transform="!isPinned(item.name) ? 'rotate-45' : ''"
+ icon="thumbtack"
+ />
+ </button>
+ </component>
+ </li>
+</template>
+
+<script src="./navigation_entry.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+
+.NavigationEntry {
+ .label {
+ flex: 1;
+ }
+
+ .menu-icon {
+ margin-right: 0.8em;
+ }
+
+ .extra-button {
+ width: 3em;
+ text-align: center;
+
+ &:last-child {
+ margin-right: -0.8em;
+ }
+ }
+
+ .menu-item {
+ display: flex;
+ box-sizing: border-box;
+ align-items: baseline;
+ height: 3.5em;
+ line-height: 3.5em;
+ padding: 0 1em;
+ width: 100%;
+ color: $fallback--link;
+ color: var(--link, $fallback--link);
+
+ &:hover {
+ background-color: $fallback--lightBg;
+ background-color: var(--selectedMenu, $fallback--lightBg);
+ color: $fallback--link;
+ color: var(--selectedMenuText, $fallback--link);
+ --faint: var(--selectedMenuFaintText, $fallback--faint);
+ --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
+ --lightText: var(--selectedMenuLightText, $fallback--lightText);
+
+ .menu-icon {
+ --icon: var(--text, $fallback--icon);
+ }
+ }
+
+ &.router-link-active {
+ font-weight: bolder;
+ background-color: $fallback--lightBg;
+ background-color: var(--selectedMenu, $fallback--lightBg);
+ color: $fallback--text;
+ color: var(--selectedMenuText, $fallback--text);
+ --faint: var(--selectedMenuFaintText, $fallback--faint);
+ --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
+ --lightText: var(--selectedMenuLightText, $fallback--lightText);
+
+ .menu-icon {
+ --icon: var(--text, $fallback--icon);
+ }
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+}
+</style>
diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js
@@ -0,0 +1,88 @@
+import { mapState } from 'vuex'
+import { TIMELINES, ROOT_ITEMS, USERNAME_ROUTES } from 'src/components/navigation/navigation.js'
+import { getListEntries, filterNavigation } from 'src/components/navigation/filter.js'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faUsers,
+ faGlobe,
+ faBookmark,
+ faEnvelope,
+ faComments,
+ faBell,
+ faInfoCircle,
+ faStream,
+ faList
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faUsers,
+ faGlobe,
+ faBookmark,
+ faEnvelope,
+ faComments,
+ faBell,
+ faInfoCircle,
+ faStream,
+ faList
+)
+
+const NavPanel = {
+ props: ['limit'],
+ methods: {
+ getRouteTo (item) {
+ if (item.routeObject) {
+ return item.routeObject
+ }
+ const route = { name: (item.anon || this.currentUser) ? item.route : item.anonRoute }
+ if (USERNAME_ROUTES.has(route.name)) {
+ route.params = { username: this.currentUser.screen_name }
+ }
+ return route
+ }
+ },
+ computed: {
+ getters () {
+ return this.$store.getters
+ },
+ ...mapState({
+ lists: getListEntries,
+ currentUser: state => state.users.currentUser,
+ followRequestCount: state => state.api.followRequests.length,
+ privateMode: state => state.instance.private,
+ federating: state => state.instance.federating,
+ pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
+ pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
+ }),
+ pinnedList () {
+ if (!this.currentUser) {
+ return [
+ { ...TIMELINES.public, name: 'public' },
+ { ...TIMELINES.twkn, name: 'twkn' },
+ { ...ROOT_ITEMS.about, name: 'about' }
+ ]
+ }
+ return filterNavigation(
+ [
+ ...Object
+ .entries({ ...TIMELINES })
+ .filter(([k]) => this.pinnedItems.has(k))
+ .map(([k, v]) => ({ ...v, name: k })),
+ ...this.lists.filter((k) => this.pinnedItems.has(k.name)),
+ ...Object
+ .entries({ ...ROOT_ITEMS })
+ .filter(([k]) => this.pinnedItems.has(k))
+ .map(([k, v]) => ({ ...v, name: k }))
+ ],
+ {
+ hasChats: this.pleromaChatMessagesAvailable,
+ isFederating: this.federating,
+ isPrivate: this.privateMode,
+ currentUser: this.currentUser
+ }
+ ).slice(0, this.limit)
+ }
+ }
+}
+
+export default NavPanel
diff --git a/src/components/navigation/navigation_pins.vue b/src/components/navigation/navigation_pins.vue
@@ -0,0 +1,76 @@
+<template>
+ <span class="NavigationPins">
+ <router-link
+ v-for="item in pinnedList"
+ :key="item.name"
+ class="pinned-item"
+ :to="getRouteTo(item)"
+ :title="item.labelRaw || $t(item.label)"
+ >
+ <FAIcon
+ v-if="item.icon"
+ fixed-width
+ :icon="item.icon"
+ />
+ <span
+ v-if="item.iconLetter"
+ class="iconLetter fa-scale-110 fa-old-padding"
+ >{{ item.iconLetter }}</span>
+ <div
+ v-if="item.badgeGetter && getters[item.badgeGetter]"
+ class="alert-dot"
+ />
+ </router-link>
+ </span>
+</template>
+
+<script src="./navigation_pins.js"></script>
+
+<style lang="scss">
+@import '../../_variables.scss';
+.NavigationPins {
+ display: flex;
+ flex-wrap: wrap;
+ overflow: hidden;
+ height: 100%;
+
+ .alert-dot {
+ border-radius: 100%;
+ height: 0.5em;
+ width: 0.5em;
+ position: absolute;
+ right: calc(50% - 0.25em);
+ top: calc(50% - 0.25em);
+ margin-left: 6px;
+ margin-top: -6px;
+ background-color: $fallback--cRed;
+ background-color: var(--badgeNotification, $fallback--cRed);
+ }
+
+ .pinned-item {
+ position: relative;
+ flex: 1 0 3em;
+ min-width: 2em;
+ text-align: center;
+ overflow: visible;
+ box-sizing: border-box;
+ height: 100%;
+
+ & .svg-inline--fa,
+ & .iconLetter {
+ margin: 0;
+ }
+
+ &.router-link-active {
+ color: $fallback--text;
+ color: var(--selectedMenuText, $fallback--text);
+ border-bottom: 4px solid;
+
+ & .svg-inline--fa,
+ & .iconLetter {
+ color: inherit;
+ }
+ }
+ }
+}
+</style>
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
@@ -5,6 +5,7 @@ import UserAvatar from '../user_avatar/user_avatar.vue'
import UserCard from '../user_card/user_card.vue'
import Timeago from '../timeago/timeago.vue'
import Report from '../report/report.vue'
+import UserLink from '../user_link/user_link.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import UserPopover from '../user_popover/user_popover.vue'
import { isStatusNotification } from '../../services/notification_utils/notification_utils.js'
@@ -50,7 +51,8 @@ const Notification = {
Status,
Report,
RichContent,
- UserPopover
+ UserPopover,
+ UserLink
},
methods: {
toggleUserExpanded () {
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
@@ -11,9 +11,10 @@
class="Notification container -muted"
>
<small>
- <router-link :to="userProfileLink">
- {{ notification.from_profile.screen_name_ui }}
- </router-link>
+ <user-link
+ :user="notification.from_profile"
+ :at="false"
+ />
</small>
<button
class="button-unstyled unmute"
@@ -174,12 +175,10 @@
v-if="notification.type === 'follow' || notification.type === 'follow_request'"
class="follow-text"
>
- <router-link
- :to="userProfileLink"
+ <user-link
class="follow-name"
- >
- @{{ notification.from_profile.screen_name_ui }}
- </router-link>
+ :user="notification.from_profile"
+ />
<div
v-if="notification.type === 'follow_request'"
style="white-space: nowrap;"
@@ -210,9 +209,9 @@
v-else-if="notification.type === 'move'"
class="move-text"
>
- <router-link :to="targetUserProfileLink">
- @{{ notification.target.screen_name_ui }}
- </router-link>
+ <user-link
+ :user="notification.target"
+ />
</div>
<Report
v-else-if="notification.type === 'pleroma:report'"
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
@@ -4,7 +4,7 @@ const Popover = {
// Action to trigger popover: either 'hover' or 'click'
trigger: String,
- // Either 'top' or 'bottom'
+ // 'top', 'bottom', 'left', 'right'
placement: String,
// Takes object with properties 'x' and 'y', values of these can be
@@ -84,6 +84,8 @@ const Popover = {
const anchorStyle = getComputedStyle(anchorEl)
const topPadding = parseFloat(anchorStyle.paddingTop)
const bottomPadding = parseFloat(anchorStyle.paddingBottom)
+ const rightPadding = parseFloat(anchorStyle.paddingRight)
+ const leftPadding = parseFloat(anchorStyle.paddingLeft)
// Screen position of the origin point for popover = center of the anchor
const origin = {
@@ -170,7 +172,7 @@ const Popover = {
if (overlayCenter) {
translateX = origin.x + horizOffset
translateY = origin.y + vertOffset
- } else {
+ } else if (this.placement !== 'right' && this.placement !== 'left') {
// Default to whatever user wished with placement prop
let usingTop = this.placement !== 'bottom'
@@ -189,6 +191,25 @@ const Popover = {
const xOffset = (this.offset && this.offset.x) || 0
translateX = origin.x + horizOffset + xOffset
+ } else {
+ // Default to whatever user wished with placement prop
+ let usingRight = this.placement !== 'left'
+
+ // Handle special cases, first force to displaying on top if there's not space on bottom,
+ // regardless of what placement value was. Then check if there's not space on top, and
+ // force to bottom, again regardless of what placement value was.
+ const rightBoundary = origin.x - anchorWidth * 0.5 + (this.removePadding ? rightPadding : 0)
+ const leftBoundary = origin.x + anchorWidth * 0.5 - (this.removePadding ? leftPadding : 0)
+ if (leftBoundary + content.offsetWidth > xBounds.max) usingRight = true
+ if (rightBoundary - content.offsetWidth < xBounds.min) usingRight = false
+
+ const xOffset = (this.offset && this.offset.x) || 0
+ translateX = usingRight
+ ? rightBoundary - xOffset - content.offsetWidth
+ : leftBoundary + xOffset
+
+ const yOffset = (this.offset && this.offset.y) || 0
+ translateY = origin.y + vertOffset + yOffset
}
this.styles = {
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
@@ -126,6 +126,13 @@
}
}
+ &.-has-submenu {
+ .chevron-icon {
+ margin-right: 0.25rem;
+ margin-left: 2rem;
+ }
+ }
+
&:active, &:hover {
background-color: $fallback--lightBg;
background-color: var(--selectedMenuPopover, $fallback--lightBg);
diff --git a/src/components/search_bar/search_bar.vue b/src/components/search_bar/search_bar.vue
@@ -47,6 +47,8 @@
class="cancel-icon fa-scale-110 fa-old-padding"
/>
</button>
+ <span class="spacer" />
+ <span class="spacer" />
</template>
</div>
</template>
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
@@ -102,11 +102,6 @@
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="listsNavigation">
- {{ $t('settings.lists_navigation') }}
- </BooleanSetting>
- </li>
- <li>
<h3>{{ $t('settings.columns') }}</h3>
</li>
<li>
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
@@ -2,6 +2,7 @@ import { mapState, mapGetters } from 'vuex'
import UserCard from '../user_card/user_card.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
+import { USERNAME_ROUTES } from 'src/components/navigation/navigation.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSignInAlt,
@@ -15,6 +16,7 @@ import {
faTachometerAlt,
faCog,
faInfoCircle,
+ faCompass,
faList
} from '@fortawesome/free-solid-svg-icons'
@@ -30,6 +32,7 @@ library.add(
faTachometerAlt,
faCog,
faInfoCircle,
+ faCompass,
faList
)
@@ -80,10 +83,16 @@ const SideDrawer = {
return this.$store.state.instance.federating
},
timelinesRoute () {
+ let name
if (this.$store.state.interface.lastTimeline) {
- return this.$store.state.interface.lastTimeline
+ name = this.$store.state.interface.lastTimeline
+ }
+ name = this.currentUser ? 'friends' : 'public-timeline'
+ if (USERNAME_ROUTES.has(name)) {
+ return { name, params: { username: this.currentUser.screen_name } }
+ } else {
+ return { name }
}
- return this.currentUser ? 'friends' : 'public-timeline'
},
...mapState({
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
@@ -47,7 +47,7 @@
v-if="currentUser || !privateMode"
@click="toggleDrawer"
>
- <router-link :to="{ name: timelinesRoute }">
+ <router-link :to="timelinesRoute">
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -195,6 +195,18 @@
v-if="currentUser"
@click="toggleDrawer"
>
+ <router-link :to="{ name: 'edit-navigation' }">
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 fa-old-padding"
+ icon="compass"
+ /> {{ $t("nav.edit_nav_mobile") }}
+ </router-link>
+ </li>
+ <li
+ v-if="currentUser"
+ @click="toggleDrawer"
+ >
<button
class="button-unstyled -link -fullwidth"
@click="doLogout"
diff --git a/src/components/status/status.js b/src/components/status/status.js
@@ -13,6 +13,7 @@ import StatusPopover from '../status_popover/status_popover.vue'
import UserPopover from '../user_popover/user_popover.vue'
import UserListPopover from '../user_list_popover/user_list_popover.vue'
import EmojiReactions from '../emoji_reactions/emoji_reactions.vue'
+import UserLink from '../user_link/user_link.vue'
import MentionsLine from 'src/components/mentions_line/mentions_line.vue'
import MentionLink from 'src/components/mention_link/mention_link.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
@@ -115,7 +116,8 @@ const Status = {
RichContent,
MentionLink,
MentionsLine,
- UserPopover
+ UserPopover,
+ UserLink
},
props: [
'statusoid',
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
@@ -25,9 +25,10 @@
class="fa-scale-110 fa-old-padding repeat-icon"
icon="retweet"
/>
- <router-link :to="userProfileLink">
- {{ status.user.screen_name_ui }}
- </router-link>
+ <user-link
+ :user="status.user"
+ :at="false"
+ />
</small>
<small
v-if="showReasonMutedThread"
@@ -164,13 +165,12 @@
>
{{ status.user.name }}
</h4>
- <router-link
+ <user-link
class="account-name"
:title="status.user.screen_name_ui"
- :to="userProfileLink"
- >
- {{ status.user.screen_name_ui }}
- </router-link>
+ :user="status.user"
+ :at="false"
+ />
<img
v-if="!!(status.user && status.user.favicon)"
class="status-favicon"
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
@@ -17,6 +17,7 @@
overflow-x: auto;
padding-top: 5px;
flex-direction: row;
+ flex: 0 0 auto;
&::after, &::before {
content: '';
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
@@ -1,7 +1,10 @@
<template>
<div :class="['Timeline', classes.root]">
<div :class="classes.header">
- <TimelineMenu v-if="!embedded" />
+ <TimelineMenu
+ v-if="!embedded"
+ :timeline-name="timelineName"
+ />
<button
v-if="showScrollTop"
class="button-unstyled scroll-to-top-button"
diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js
@@ -1,6 +1,8 @@
import Popover from '../popover/popover.vue'
-import TimelineMenuContent from './timeline_menu_content.vue'
+import NavigationEntry from 'src/components/navigation/navigation_entry.vue'
+import { ListsMenuContent } from '../lists_menu/lists_menu_content.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
+import { TIMELINES } from 'src/components/navigation/navigation.js'
import {
faChevronDown
} from '@fortawesome/free-solid-svg-icons'
@@ -22,11 +24,13 @@ export const timelineNames = () => {
const TimelineMenu = {
components: {
Popover,
- TimelineMenuContent
+ NavigationEntry,
+ ListsMenuContent
},
data () {
return {
- isOpen: false
+ isOpen: false,
+ timelinesList: Object.entries(TIMELINES).map(([k, v]) => ({ ...v, name: k }))
}
},
created () {
@@ -34,6 +38,12 @@ const TimelineMenu = {
this.$store.dispatch('setLastTimeline', this.$route.name)
}
},
+ computed: {
+ useListsMenu () {
+ const route = this.$route.name
+ return route === 'lists-timeline'
+ }
+ },
methods: {
openMenu () {
// $nextTick is too fast, animation won't play back but
diff --git a/src/components/timeline_menu/timeline_menu.vue b/src/components/timeline_menu/timeline_menu.vue
@@ -10,7 +10,19 @@
@close="() => isOpen = false"
>
<template #content>
- <TimelineMenuContent />
+ <ListsMenuContent
+ v-if="useListsMenu"
+ :show-pin="false"
+ class="timelines"
+ />
+ <ul v-else>
+ <NavigationEntry
+ v-for="item in timelinesList"
+ :key="item.name"
+ :show-pin="false"
+ :item="item"
+ />
+ </ul>
</template>
<template #trigger>
<span class="button-unstyled title timeline-menu-title">
@@ -138,8 +150,7 @@
background-color: $fallback--lightBg;
background-color: var(--selectedMenu, $fallback--lightBg);
color: $fallback--text;
- color: var(--selectedMenuText, $fallback--text);
- --faint: var(--selectedMenuFaintText, $fallback--faint);
+ color: var(--selectedMenuText, $fallback--text); --faint: var(--selectedMenuFaintText, $fallback--faint);
--faintLink: var(--selectedMenuFaintLink, $fallback--faint);
--lightText: var(--selectedMenuLightText, $fallback--lightText);
--icon: var(--selectedMenuIcon, $fallback--icon);
diff --git a/src/components/timeline_menu/timeline_menu_content.js b/src/components/timeline_menu/timeline_menu_content.js
@@ -1,29 +0,0 @@
-import { mapState } from 'vuex'
-import { library } from '@fortawesome/fontawesome-svg-core'
-import {
- faUsers,
- faGlobe,
- faBookmark,
- faEnvelope,
- faHome
-} from '@fortawesome/free-solid-svg-icons'
-
-library.add(
- faUsers,
- faGlobe,
- faBookmark,
- faEnvelope,
- faHome
-)
-
-const TimelineMenuContent = {
- computed: {
- ...mapState({
- currentUser: state => state.users.currentUser,
- privateMode: state => state.instance.private,
- federating: state => state.instance.federating
- })
- }
-}
-
-export default TimelineMenuContent
diff --git a/src/components/timeline_menu/timeline_menu_content.vue b/src/components/timeline_menu/timeline_menu_content.vue
@@ -1,66 +0,0 @@
-<template>
- <ul>
- <li v-if="currentUser">
- <router-link
- class="menu-item"
- :to="{ name: 'friends' }"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110 fa-old-padding "
- icon="home"
- />{{ $t("nav.home_timeline") }}
- </router-link>
- </li>
- <li v-if="currentUser || !privateMode">
- <router-link
- class="menu-item"
- :to="{ name: 'public-timeline' }"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110 fa-old-padding "
- icon="users"
- />{{ $t("nav.public_tl") }}
- </router-link>
- </li>
- <li v-if="federating && (currentUser || !privateMode)">
- <router-link
- class="menu-item"
- :to="{ name: 'public-external-timeline' }"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110 fa-old-padding "
- icon="globe"
- />{{ $t("nav.twkn") }}
- </router-link>
- </li>
- <li v-if="currentUser">
- <router-link
- class="menu-item"
- :to="{ name: 'bookmarks'}"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110 fa-old-padding "
- icon="bookmark"
- />{{ $t("nav.bookmarks") }}
- </router-link>
- </li>
- <li v-if="currentUser">
- <router-link
- class="menu-item"
- :to="{ name: 'dms', params: { username: currentUser.screen_name } }"
- >
- <FAIcon
- fixed-width
- class="fa-scale-110 fa-old-padding "
- icon="envelope"
- />{{ $t("nav.dms") }}
- </router-link>
- </li>
- </ul>
-</template>
-
-<script src="./timeline_menu_content.js"></script>
diff --git a/src/components/unicode_domain_indicator/unicode_domain_indicator.vue b/src/components/unicode_domain_indicator/unicode_domain_indicator.vue
@@ -0,0 +1,26 @@
+<template>
+ <FAIcon
+ v-if="user && user.screen_name_ui_contains_non_ascii"
+ icon="code"
+ :title="$t('unicode_domain_indicator.tooltip')"
+ />
+</template>
+
+<script>
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faCode
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faCode
+)
+
+const UnicodeDomainIndicator = {
+ props: {
+ user: Object
+ }
+}
+
+export default UnicodeDomainIndicator
+</script>
diff --git a/src/components/update_notification/update_notification.js b/src/components/update_notification/update_notification.js
@@ -38,7 +38,7 @@ const UpdateNotification = {
return !this.$store.state.instance.disableUpdateNotification &&
this.$store.state.users.currentUser &&
this.$store.state.serverSideStorage.flagStorage.updateCounter < CURRENT_UPDATE_COUNTER &&
- !this.$store.state.serverSideStorage.flagStorage.dontShowUpdateNotifs
+ !this.$store.state.serverSideStorage.prefsStorage.simple.dontShowUpdateNotifs
}
},
methods: {
@@ -48,7 +48,7 @@ const UpdateNotification = {
neverShowAgain () {
this.toggleShow()
this.$store.commit('setFlag', { flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER })
- this.$store.commit('setFlag', { flag: 'dontShowUpdateNotifs', value: 1 })
+ this.$store.commit('setPreference', { path: 'simple.dontShowUpdateNotifs', value: true })
this.$store.dispatch('pushServerSideStorage')
},
dismiss () {
diff --git a/src/components/update_notification/update_notification.vue b/src/components/update_notification/update_notification.vue
@@ -60,7 +60,7 @@
<template #linkToArtist>
<a
target="_blank"
- href="https://post.ebin.club/pipivovott"
+ href="https://post.ebin.club/users/pipivovott"
>pipivovott</a>
</template>
</i18n-t>
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
@@ -5,6 +5,7 @@ import FollowButton from '../follow_button/follow_button.vue'
import ModerationTools from '../moderation_tools/moderation_tools.vue'
import AccountActions from '../account_actions/account_actions.vue'
import Select from '../select/select.vue'
+import UserLink from '../user_link/user_link.vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import { mapGetters } from 'vuex'
@@ -134,7 +135,8 @@ export default {
ProgressButton,
FollowButton,
Select,
- RichContent
+ RichContent,
+ UserLink
},
methods: {
muteUser () {
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
@@ -106,13 +106,10 @@
</button>
</div>
<div class="bottom-line">
- <router-link
+ <user-link
class="user-screen-name"
- :title="user.screen_name_ui"
- :to="userProfileLink(user)"
- >
- @{{ user.screen_name_ui }}
- </router-link>
+ :user="user"
+ />
<template v-if="!hideBio">
<span
v-if="user.deactivated"
diff --git a/src/components/user_link/user_link.vue b/src/components/user_link/user_link.vue
@@ -0,0 +1,38 @@
+<template>
+ <router-link
+ :title="user.screen_name_ui"
+ :to="userProfileLink(user)"
+ >
+ {{ at ? '@' : '' }}{{ user.screen_name_ui }}<UnicodeDomainIndicator
+ :user="user"
+ />
+ </router-link>
+</template>
+
+<script>
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
+import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
+
+const UserLink = {
+ props: {
+ user: Object,
+ at: {
+ type: Boolean,
+ default: true
+ }
+ },
+ components: {
+ UnicodeDomainIndicator
+ },
+ methods: {
+ userProfileLink (user) {
+ return generateProfileLink(
+ user.id, user.screen_name,
+ this.$store.state.instance.restrictedNicknames
+ )
+ }
+ }
+}
+
+export default UserLink
+</script>
diff --git a/src/components/user_list_menu/user_list_menu.js b/src/components/user_list_menu/user_list_menu.js
@@ -0,0 +1,93 @@
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faChevronRight } from '@fortawesome/free-solid-svg-icons'
+import { mapState } from 'vuex'
+
+import DialogModal from '../dialog_modal/dialog_modal.vue'
+import Popover from '../popover/popover.vue'
+
+library.add(faChevronRight)
+
+const UserListMenu = {
+ props: [
+ 'user'
+ ],
+ data () {
+ return {}
+ },
+ components: {
+ DialogModal,
+ Popover
+ },
+ created () {
+ this.$store.dispatch('fetchUserInLists', this.user.id)
+ },
+ computed: {
+ ...mapState({
+ allLists: state => state.lists.allLists
+ }),
+ inListsSet () {
+ return new Set(this.user.inLists.map(x => x.id))
+ },
+ lists () {
+ if (!this.user.inLists) return []
+ return this.allLists.map(list => ({
+ ...list,
+ inList: this.inListsSet.has(list.id)
+ }))
+ }
+ },
+ methods: {
+ toggleList (listId) {
+ if (this.inListsSet.has(listId)) {
+ this.$store.dispatch('removeListAccount', { accountId: this.user.id, listId }).then((response) => {
+ if (!response.ok) { return }
+ this.$store.dispatch('fetchUserInLists', this.user.id)
+ })
+ } else {
+ this.$store.dispatch('addListAccount', { accountId: this.user.id, listId }).then((response) => {
+ if (!response.ok) { return }
+ this.$store.dispatch('fetchUserInLists', this.user.id)
+ })
+ }
+ },
+ toggleRight (right) {
+ const store = this.$store
+ if (this.user.rights[right]) {
+ store.state.api.backendInteractor.deleteRight({ user: this.user, right }).then(response => {
+ if (!response.ok) { return }
+ store.commit('updateRight', { user: this.user, right, value: false })
+ })
+ } else {
+ store.state.api.backendInteractor.addRight({ user: this.user, right }).then(response => {
+ if (!response.ok) { return }
+ store.commit('updateRight', { user: this.user, right, value: true })
+ })
+ }
+ },
+ toggleActivationStatus () {
+ this.$store.dispatch('toggleActivationStatus', { user: this.user })
+ },
+ deleteUserDialog (show) {
+ this.showDeleteUserDialog = show
+ },
+ deleteUser () {
+ const store = this.$store
+ const user = this.user
+ const { id, name } = user
+ store.state.api.backendInteractor.deleteUser({ user })
+ .then(e => {
+ this.$store.dispatch('markStatusesAsDeleted', status => user.id === status.user.id)
+ const isProfile = this.$route.name === 'external-user-profile' || this.$route.name === 'user-profile'
+ const isTargetUser = this.$route.params.name === name || this.$route.params.id === id
+ if (isProfile && isTargetUser) {
+ window.history.back()
+ }
+ })
+ },
+ setToggled (value) {
+ this.toggled = value
+ }
+ }
+}
+
+export default UserListMenu
diff --git a/src/components/user_list_menu/user_list_menu.vue b/src/components/user_list_menu/user_list_menu.vue
@@ -0,0 +1,38 @@
+<template>
+ <div class="UserListMenu">
+ <Popover
+ trigger="hover"
+ placement="left"
+ remove-padding
+ >
+ <template #content>
+ <div class="dropdown-menu">
+ <button
+ v-for="list in lists"
+ :key="list.id"
+ class="button-default dropdown-item"
+ @click="toggleList(list.id)"
+ >
+ <span
+ class="menu-checkbox"
+ :class="{ 'menu-checkbox-checked': list.inList }"
+ />
+ {{ list.title }}
+ </button>
+ </div>
+ </template>
+ <template #trigger>
+ <button class="btn button-default dropdown-item -has-submenu">
+ {{ $t('lists.manage_lists') }}
+ <FAIcon
+ class="chevron-icon"
+ size="lg"
+ icon="chevron-right"
+ />
+ </button>
+ </template>
+ </Popover>
+ </div>
+</template>
+
+<script src="./user_list_menu.js"></script>
diff --git a/src/components/user_list_popover/user_list_popover.js b/src/components/user_list_popover/user_list_popover.js
@@ -1,5 +1,6 @@
import { defineAsyncComponent } from 'vue'
import RichContent from 'src/components/rich_content/rich_content.jsx'
+import UnicodeDomainIndicator from '../unicode_domain_indicator/unicode_domain_indicator.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'
@@ -15,6 +16,7 @@ const UserListPopover = {
],
components: {
RichContent,
+ UnicodeDomainIndicator,
Popover: defineAsyncComponent(() => import('../popover/popover.vue')),
UserAvatar: defineAsyncComponent(() => import('../user_avatar/user_avatar.vue'))
},
diff --git a/src/components/user_list_popover/user_list_popover.vue b/src/components/user_list_popover/user_list_popover.vue
@@ -29,7 +29,7 @@
:emoji="user.emoji"
/>
<!-- eslint-enable vue/no-v-html -->
- <span class="user-list-screen-name">{{ user.screen_name_ui }}</span>
+ <span class="user-list-screen-name">{{ user.screen_name_ui }}</span><UnicodeDomainIndicator :user="user" />
</div>
</div>
</template>
diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js
@@ -2,13 +2,15 @@ import Status from '../status/status.vue'
import List from '../list/list.vue'
import Checkbox from '../checkbox/checkbox.vue'
import Modal from '../modal/modal.vue'
+import UserLink from '../user_link/user_link.vue'
const UserReportingModal = {
components: {
Status,
List,
Checkbox,
- Modal
+ Modal,
+ UserLink
},
data () {
return {
diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue
@@ -5,9 +5,13 @@
>
<div class="user-reporting-panel panel">
<div class="panel-heading">
- <div class="title">
- {{ $t('user_reporting.title', [user.screen_name_ui]) }}
- </div>
+ <i18n-t
+ tag="div"
+ keypath="user_reporting.title"
+ class="title"
+ >
+ <UserLink :user="user" />
+ </i18n-t>
</div>
<div class="panel-body">
<div class="user-reporting-panel-left">
diff --git a/src/i18n/en.json b/src/i18n/en.json
@@ -80,11 +80,16 @@
"confirm": "Confirm",
"verify": "Verify",
"close": "Close",
+ "undo": "Undo",
+ "yes": "Yes",
+ "no": "No",
"peek": "Peek",
"role": {
"admin": "Admin",
"moderator": "Moderator"
},
+ "unpin": "Unpin item",
+ "pin": "Pin item",
"flash_content": "Click to show Flash content using Ruffle (Experimental, may not work).",
"flash_security": "Note that this can be potentially dangerous since Flash content is still arbitrary code.",
"flash_fail": "Failed to load flash content, see console for details.",
@@ -149,7 +154,10 @@
"preferences": "Preferences",
"timelines": "Timelines",
"chats": "Chats",
- "lists": "Lists"
+ "lists": "Lists",
+ "edit_nav_mobile": "Customize navigation bar",
+ "edit_pinned": "Edit pinned items",
+ "edit_finish": "Done editing"
},
"notifications": {
"broken_favorite": "Unknown status, searching for it…",
@@ -987,7 +995,18 @@
"create": "Create",
"save": "Save changes",
"delete": "Delete list",
- "following_only": "Limit to Following"
+ "following_only": "Limit to Following",
+ "manage_lists": "Manage lists",
+ "manage_members": "Manage list members",
+ "add_members": "Search for more users",
+ "remove_from_list": "Remove from list",
+ "add_to_list": "Add to list",
+ "is_in_list": "Already in list",
+ "editing_list": "Editing list {listTitle}",
+ "creating_list": "Creating new list",
+ "update_title": "Save Title",
+ "really_delete": "Really delete list?",
+ "error": "Error manipulating lists: {0}"
},
"file_type": {
"audio": "Audio",
@@ -1006,5 +1025,8 @@
"update_changelog": "For more details on what's changed, see {theFullChangelog}.",
"update_changelog_here": "the full changelog",
"art_by": "Art by {linkToArtist}"
+ },
+ "unicode_domain_indicator": {
+ "tooltip": "This domain contains non-ascii characters."
}
}
diff --git a/src/modules/api.js b/src/modules/api.js
@@ -15,6 +15,9 @@ const api = {
mastoUserSocketStatus: null,
followRequests: []
},
+ getters: {
+ followRequestCount: state => state.api.followRequests.length
+ },
mutations: {
setBackendInteractor (state, backendInteractor) {
state.backendInteractor = backendInteractor
diff --git a/src/modules/config.js b/src/modules/config.js
@@ -89,7 +89,6 @@ export const defaultState = {
contentColumnWidth: '45rem',
notifsColumnWidth: '25rem',
navbarColumnStretch: false,
- listsNavigation: false,
greentext: undefined, // instance default
useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default
diff --git a/src/modules/lists.js b/src/modules/lists.js
@@ -9,27 +9,43 @@ export const mutations = {
setLists (state, value) {
state.allLists = value
},
- setList (state, { id, title }) {
- if (!state.allListsObject[id]) {
- state.allListsObject[id] = {}
+ setList (state, { listId, title }) {
+ if (!state.allListsObject[listId]) {
+ state.allListsObject[listId] = { accountIds: [] }
}
- state.allListsObject[id].title = title
+ state.allListsObject[listId].title = title
- if (!find(state.allLists, { id })) {
- state.allLists.push({ id, title })
+ const entry = find(state.allLists, { id: listId })
+ if (!entry) {
+ state.allLists.push({ id: listId, title })
} else {
- find(state.allLists, { id }).title = title
+ entry.title = title
}
},
- setListAccounts (state, { id, accountIds }) {
- if (!state.allListsObject[id]) {
- state.allListsObject[id] = {}
+ setListAccounts (state, { listId, accountIds }) {
+ if (!state.allListsObject[listId]) {
+ state.allListsObject[listId] = { accountIds: [] }
}
- state.allListsObject[id].accountIds = accountIds
+ state.allListsObject[listId].accountIds = accountIds
},
- deleteList (state, { id }) {
- delete state.allListsObject[id]
- remove(state.allLists, list => list.id === id)
+ addListAccount (state, { listId, accountId }) {
+ if (!state.allListsObject[listId]) {
+ state.allListsObject[listId] = { accountIds: [] }
+ }
+ state.allListsObject[listId].accountIds.push(accountId)
+ },
+ removeListAccount (state, { listId, accountId }) {
+ if (!state.allListsObject[listId]) {
+ state.allListsObject[listId] = { accountIds: [] }
+ }
+ const { accountIds } = state.allListsObject[listId]
+ const set = new Set(accountIds)
+ set.delete(accountId)
+ state.allListsObject[listId].accountIds = [...set]
+ },
+ deleteList (state, { listId }) {
+ delete state.allListsObject[listId]
+ remove(state.allLists, list => list.id === listId)
}
}
@@ -40,37 +56,57 @@ const actions = {
createList ({ rootState, commit }, { title }) {
return rootState.api.backendInteractor.createList({ title })
.then((list) => {
- commit('setList', { id: list.id, title })
+ commit('setList', { listId: list.id, title })
return list
})
},
- fetchList ({ rootState, commit }, { id }) {
- return rootState.api.backendInteractor.getList({ id })
- .then((list) => commit('setList', { id: list.id, title: list.title }))
+ fetchList ({ rootState, commit }, { listId }) {
+ return rootState.api.backendInteractor.getList({ listId })
+ .then((list) => commit('setList', { listId: list.id, title: list.title }))
},
- fetchListAccounts ({ rootState, commit }, { id }) {
- return rootState.api.backendInteractor.getListAccounts({ id })
- .then((accountIds) => commit('setListAccounts', { id, accountIds }))
+ fetchListAccounts ({ rootState, commit }, { listId }) {
+ return rootState.api.backendInteractor.getListAccounts({ listId })
+ .then((accountIds) => commit('setListAccounts', { listId, accountIds }))
},
- setList ({ rootState, commit }, { id, title }) {
- rootState.api.backendInteractor.updateList({ id, title })
- commit('setList', { id, title })
+ setList ({ rootState, commit }, { listId, title }) {
+ rootState.api.backendInteractor.updateList({ listId, title })
+ commit('setList', { listId, title })
},
- setListAccounts ({ rootState, commit }, { id, accountIds }) {
- const saved = rootState.lists.allListsObject[id].accountIds || []
+ setListAccounts ({ rootState, commit }, { listId, accountIds }) {
+ const saved = rootState.lists.allListsObject[listId].accountIds || []
const added = accountIds.filter(id => !saved.includes(id))
const removed = saved.filter(id => !accountIds.includes(id))
- commit('setListAccounts', { id, accountIds })
+ commit('setListAccounts', { listId, accountIds })
if (added.length > 0) {
- rootState.api.backendInteractor.addAccountsToList({ id, accountIds: added })
+ rootState.api.backendInteractor.addAccountsToList({ listId, accountIds: added })
}
if (removed.length > 0) {
- rootState.api.backendInteractor.removeAccountsFromList({ id, accountIds: removed })
+ rootState.api.backendInteractor.removeAccountsFromList({ listId, accountIds: removed })
}
},
- deleteList ({ rootState, commit }, { id }) {
- rootState.api.backendInteractor.deleteList({ id })
- commit('deleteList', { id })
+ addListAccount ({ rootState, commit }, { listId, accountId }) {
+ return rootState
+ .api
+ .backendInteractor
+ .addAccountsToList({ listId, accountIds: [accountId] })
+ .then((result) => {
+ commit('addListAccount', { listId, accountId })
+ return result
+ })
+ },
+ removeListAccount ({ rootState, commit }, { listId, accountId }) {
+ return rootState
+ .api
+ .backendInteractor
+ .removeAccountsFromList({ listId, accountIds: [accountId] })
+ .then((result) => {
+ commit('removeListAccount', { listId, accountId })
+ return result
+ })
+ },
+ deleteList ({ rootState, commit }, { listId }) {
+ rootState.api.backendInteractor.deleteList({ listId })
+ commit('deleteList', { listId })
}
}
diff --git a/src/modules/serverSideStorage.js b/src/modules/serverSideStorage.js
@@ -1,5 +1,5 @@
import { toRaw } from 'vue'
-import { isEqual, cloneDeep } from 'lodash'
+import { isEqual, cloneDeep, set, get, clamp, flatten, groupBy, findLastIndex, takeRight } from 'lodash'
import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js'
export const VERSION = 1
@@ -14,14 +14,21 @@ export const defaultState = {
// storage of flags - stuff that can only be set and incremented
flagStorage: {
updateCounter: 0, // Counter for most recent update notification seen
- // TODO move to prefsStorage when that becomes a thing since only way
- // this can be reset is by complete reset of all flags
- dontShowUpdateNotifs: 0, // if user chose to not show update notifications ever again
reset: 0 // special flag that can be used to force-reset all flags, debug purposes only
// special reset codes:
// 1000: trim keys to those known by currently running FE
// 1001: same as above + reset everything to 0
},
+ prefsStorage: {
+ _journal: [],
+ simple: {
+ dontShowUpdateNotifs: false,
+ collapseNav: false
+ },
+ collections: {
+ pinnedNavItems: ['home', 'dms', 'chats']
+ }
+ },
// raw data
raw: null,
// local cache
@@ -33,14 +40,43 @@ export const newUserFlags = {
updateCounter: CURRENT_UPDATE_COUNTER // new users don't need to see update notification
}
-const _wrapData = (data) => ({
+export const _moveItemInArray = (array, value, movement) => {
+ const oldIndex = array.indexOf(value)
+ const newIndex = oldIndex + movement
+ const newArray = [...array]
+ // remove old
+ newArray.splice(oldIndex, 1)
+ // add new
+ newArray.splice(clamp(newIndex, 0, newArray.length + 1), 0, value)
+ return newArray
+}
+
+const _wrapData = (data, userName) => ({
...data,
+ _user: userName,
_timestamp: Date.now(),
_version: VERSION
})
const _checkValidity = (data) => data._timestamp > 0 && data._version > 0
+const _verifyPrefs = (state) => {
+ state.prefsStorage = state.prefsStorage || {
+ simple: {},
+ collections: {}
+ }
+ Object.entries(defaultState.prefsStorage.simple).forEach(([k, v]) => {
+ if (typeof v === 'number' || typeof v === 'boolean') return
+ console.warn(`Preference simple.${k} as invalid type, reinitializing`)
+ set(state.prefsStorage.simple, k, defaultState.prefsStorage.simple[k])
+ })
+ Object.entries(defaultState.prefsStorage.collections).forEach(([k, v]) => {
+ if (Array.isArray(v)) return
+ console.warn(`Preference collections.${k} as invalid type, reinitializing`)
+ set(state.prefsStorage.collections, k, defaultState.prefsStorage.collections[k])
+ })
+}
+
export const _getRecentData = (cache, live) => {
const result = { recent: null, stale: null, needUpload: false }
const cacheValid = _checkValidity(cache || {})
@@ -85,6 +121,8 @@ export const _getAllFlags = (recent, stale) => {
}
export const _mergeFlags = (recent, stale, allFlagKeys) => {
+ if (!stale.flagStorage) return recent.flagStorage
+ if (!recent.flagStorage) return stale.flagStorage
return Object.fromEntries(allFlagKeys.map(flag => {
const recentFlag = recent.flagStorage[flag]
const staleFlag = stale.flagStorage[flag]
@@ -93,6 +131,88 @@ export const _mergeFlags = (recent, stale, allFlagKeys) => {
}))
}
+const _mergeJournal = (...journals) => {
+ // Ignore invalid journal entries
+ const allJournals = flatten(
+ journals.map(j => Array.isArray(j) ? j : [])
+ ).filter(entry =>
+ Object.prototype.hasOwnProperty.call(entry, 'path') &&
+ Object.prototype.hasOwnProperty.call(entry, 'operation') &&
+ Object.prototype.hasOwnProperty.call(entry, 'args') &&
+ Object.prototype.hasOwnProperty.call(entry, 'timestamp')
+ )
+ const grouped = groupBy(allJournals, 'path')
+ const trimmedGrouped = Object.entries(grouped).map(([path, journal]) => {
+ // side effect
+ journal.sort((a, b) => a.timestamp > b.timestamp ? 1 : -1)
+
+ if (path.startsWith('collections')) {
+ const lastRemoveIndex = findLastIndex(journal, ({ operation }) => operation === 'removeFromCollection')
+ // everything before last remove is unimportant
+ if (lastRemoveIndex > 0) {
+ return journal.slice(lastRemoveIndex)
+ } else {
+ // everything else doesn't need trimming
+ return journal
+ }
+ } else if (path.startsWith('simple')) {
+ // Only the last record is important
+ return takeRight(journal)
+ } else {
+ return journal
+ }
+ })
+ return flatten(trimmedGrouped)
+ .sort((a, b) => a.timestamp > b.timestamp ? 1 : -1)
+}
+
+export const _mergePrefs = (recent, stale, allFlagKeys) => {
+ if (!stale) return recent
+ if (!recent) return stale
+ const { _journal: recentJournal, ...recentData } = recent
+ const { _journal: staleJournal } = stale
+ /** Journal entry format:
+ * path: path to entry in prefsStorage
+ * timestamp: timestamp of the change
+ * operation: operation type
+ * arguments: array of arguments, depends on operation type
+ *
+ * currently only supported operation type is "set" which just sets the value
+ * to requested one. Intended only to be used with simple preferences (boolean, number)
+ * shouldn't be used with collections!
+ */
+ const resultOutput = { ...recentData }
+ const totalJournal = _mergeJournal(staleJournal, recentJournal)
+ totalJournal.forEach(({ path, timestamp, operation, command, args }) => {
+ if (path.startsWith('_')) {
+ console.error(`journal contains entry to edit internal (starts with _) field '${path}', something is incorrect here, ignoring.`)
+ return
+ }
+ switch (operation) {
+ case 'set':
+ set(resultOutput, path, args[0])
+ break
+ case 'addToCollection':
+ set(resultOutput, path, Array.from(new Set(get(resultOutput, path)).add(args[0])))
+ break
+ case 'removeFromCollection': {
+ const newSet = new Set(get(resultOutput, path))
+ newSet.delete(args[0])
+ set(resultOutput, path, Array.from(newSet))
+ break
+ }
+ case 'reorderCollection': {
+ const [value, movement] = args
+ set(resultOutput, path, _moveItemInArray(get(resultOutput, path), value, movement))
+ break
+ }
+ default:
+ console.error(`Unknown journal operation: '${operation}', did we forget to run reverse migrations beforehand?`)
+ }
+ })
+ return { ...resultOutput, _journal: totalJournal }
+}
+
export const _resetFlags = (totalFlags, knownKeys = defaultState.flagStorage) => {
let result = { ...totalFlags }
const allFlagKeys = Object.keys(totalFlags)
@@ -149,10 +269,17 @@ export const _doMigrations = (cache) => {
}
export const mutations = {
+ clearServerSideStorage (state, userData) {
+ state = { ...cloneDeep(defaultState) }
+ },
setServerSideStorage (state, userData) {
const live = userData.storage
state.raw = live
let cache = state.cache
+ if (cache && cache._user !== userData.fqn) {
+ console.warn('cache belongs to another user! reinitializing local cache!')
+ cache = null
+ }
cache = _doMigrations(cache)
@@ -165,7 +292,8 @@ export const mutations = {
if (recent === null) {
console.debug(`Data is empty, initializing for ${userNew ? 'new' : 'existing'} user`)
recent = _wrapData({
- flagStorage: { ...flagsTemplate }
+ flagStorage: { ...flagsTemplate },
+ prefsStorage: { ...defaultState.prefsStorage }
})
}
@@ -180,17 +308,23 @@ export const mutations = {
const allFlagKeys = _getAllFlags(recent, stale)
let totalFlags
+ let totalPrefs
if (dirty) {
// Merge the flags
- console.debug('Merging the flags...')
+ console.debug('Merging the data...')
totalFlags = _mergeFlags(recent, stale, allFlagKeys)
+ _verifyPrefs(recent)
+ _verifyPrefs(stale)
+ totalPrefs = _mergePrefs(recent.prefsStorage, stale.prefsStorage)
} else {
totalFlags = recent.flagStorage
+ totalPrefs = recent.prefsStorage
}
totalFlags = _resetFlags(totalFlags)
- recent.flagStorage = totalFlags
+ recent.flagStorage = { ...flagsTemplate, ...totalFlags }
+ recent.prefsStorage = { ...defaultState.prefsStorage, ...totalPrefs }
state.dirty = dirty || needsUpload
state.cache = recent
@@ -199,10 +333,72 @@ export const mutations = {
state.cache._timestamp = Math.min(stale._timestamp, recent._timestamp)
}
state.flagStorage = state.cache.flagStorage
+ state.prefsStorage = state.cache.prefsStorage
},
setFlag (state, { flag, value }) {
state.flagStorage[flag] = value
state.dirty = true
+ },
+ setPreference (state, { path, value }) {
+ if (path.startsWith('_')) {
+ console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
+ return
+ }
+ set(state.prefsStorage, path, value)
+ state.prefsStorage._journal = [
+ ...state.prefsStorage._journal,
+ { operation: 'set', path, args: [value], timestamp: Date.now() }
+ ]
+ state.dirty = true
+ },
+ addCollectionPreference (state, { path, value }) {
+ if (path.startsWith('_')) {
+ console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
+ return
+ }
+ const collection = new Set(get(state.prefsStorage, path))
+ collection.add(value)
+ set(state.prefsStorage, path, [...collection])
+ state.prefsStorage._journal = [
+ ...state.prefsStorage._journal,
+ { operation: 'addToCollection', path, args: [value], timestamp: Date.now() }
+ ]
+ state.dirty = true
+ },
+ removeCollectionPreference (state, { path, value }) {
+ if (path.startsWith('_')) {
+ console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
+ return
+ }
+ const collection = new Set(get(state.prefsStorage, path))
+ collection.delete(value)
+ set(state.prefsStorage, path, [...collection])
+ state.prefsStorage._journal = [
+ ...state.prefsStorage._journal,
+ { operation: 'removeFromCollection', path, args: [value], timestamp: Date.now() }
+ ]
+ state.dirty = true
+ },
+ reorderCollectionPreference (state, { path, value, movement }) {
+ if (path.startsWith('_')) {
+ console.error(`tried to edit internal (starts with _) field '${path}', ignoring.`)
+ return
+ }
+ const collection = get(state.prefsStorage, path)
+ const newCollection = _moveItemInArray(collection, value, movement)
+ set(state.prefsStorage, path, newCollection)
+ state.prefsStorage._journal = [
+ ...state.prefsStorage._journal,
+ { operation: 'arrangeCollection', path, args: [value], timestamp: Date.now() }
+ ]
+ state.dirty = true
+ },
+ updateCache (state, { username }) {
+ state.prefsStorage._journal = _mergeJournal(state.prefsStorage._journal)
+ state.cache = _wrapData({
+ flagStorage: toRaw(state.flagStorage),
+ prefsStorage: toRaw(state.prefsStorage)
+ }, username)
}
}
@@ -214,15 +410,16 @@ const serverSideStorage = {
actions: {
pushServerSideStorage ({ state, rootState, commit }, { force = false } = {}) {
const needPush = state.dirty || force
+ console.log(needPush)
if (!needPush) return
- state.cache = _wrapData({
- flagStorage: toRaw(state.flagStorage)
- })
+ commit('updateCache', { username: rootState.users.currentUser.fqn })
const params = { pleroma_settings_store: { 'pleroma-fe': state.cache } }
rootState.api.backendInteractor
.updateProfile({ params })
- .then((user) => commit('setServerSideStorage', user))
- state.dirty = false
+ .then((user) => {
+ commit('setServerSideStorage', user)
+ state.dirty = false
+ })
}
}
}
diff --git a/src/modules/users.js b/src/modules/users.js
@@ -171,6 +171,9 @@ export const mutations = {
state.relationships[relationship.id] = relationship
})
},
+ updateUserInLists (state, { id, inLists }) {
+ state.usersObject[id].inLists = inLists
+ },
saveBlockIds (state, blockIds) {
state.currentUser.blockIds = blockIds
},
@@ -298,6 +301,12 @@ const users = {
.then((relationships) => store.commit('updateUserRelationship', relationships))
}
},
+ fetchUserInLists (store, id) {
+ if (store.state.currentUser) {
+ store.rootState.api.backendInteractor.fetchUserInLists({ id })
+ .then((inLists) => store.commit('updateUserInLists', { id, inLists }))
+ }
+ },
fetchBlocks (store) {
return store.rootState.api.backendInteractor.fetchBlocks()
.then((blocks) => {
@@ -509,6 +518,7 @@ const users = {
store.dispatch('stopFetchingTimeline', 'friends')
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
store.dispatch('stopFetchingNotifications')
+ store.dispatch('stopFetchingLists')
store.dispatch('stopFetchingFollowRequests')
store.commit('clearNotifications')
store.commit('resetStatuses')
@@ -516,6 +526,7 @@ const users = {
store.dispatch('setLastTimeline', 'public-timeline')
store.dispatch('setLayoutWidth', windowWidth())
store.dispatch('setLayoutHeight', windowHeight())
+ store.commit('clearServerSideStorage')
})
},
loginUser (store, accessToken) {
@@ -562,6 +573,12 @@ const users = {
store.dispatch('startFetchingChats')
}
+ store.dispatch('startFetchingLists')
+
+ if (user.locked) {
+ store.dispatch('startFetchingFollowRequests')
+ }
+
if (store.getters.mergedConfig.useStreamingApi) {
store.dispatch('fetchTimeline', 'friends', { since: null })
store.dispatch('fetchNotifications', { since: null })
diff --git a/src/panel.scss b/src/panel.scss
@@ -46,7 +46,7 @@
.panel-footer {
--panel-heading-height-padding: 0.6em;
--__panel-heading-height: 3.2em;
- --__panel-heading-height-inner: calc(var(--__panel-heading-height) - 2 * var(--panel-heading-height-padding));
+ --__panel-heading-height-inner: calc(var(--__panel-heading-height) - 2 * var(--panel-heading-height-padding, 0));
position: relative;
box-sizing: border-box;
@@ -57,7 +57,7 @@
grid-column-gap: 0.5em;
flex: none;
background-size: cover;
- padding: 0.6em;
+ padding: var(--panel-heading-height-padding);
height: var(--__panel-heading-height);
line-height: var(--__panel-heading-height-inner);
z-index: 4;
@@ -147,6 +147,15 @@
color: var(--panelLink, $fallback--link);
}
+ .button-unstyled:hover,
+ a:hover {
+ i[class*=icon-],
+ .svg-inline--fa,
+ .iconLetter {
+ color: var(--panelText);
+ }
+ }
+
.faint {
background-color: transparent;
color: $fallback--faint;
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
@@ -53,6 +53,7 @@ const MASTODON_USER_URL = '/api/v1/accounts'
const MASTODON_USER_LOOKUP_URL = '/api/v1/accounts/lookup'
const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships'
const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses`
+const MASTODON_USER_IN_LISTS = id => `/api/v1/accounts/${id}/lists`
const MASTODON_LIST_URL = id => `/api/v1/lists/${id}`
const MASTODON_LIST_TIMELINE_URL = id => `/api/v1/timelines/list/${id}`
const MASTODON_LIST_ACCOUNTS_URL = id => `/api/v1/lists/${id}/accounts`
@@ -263,6 +264,13 @@ const unfollowUser = ({ id, credentials }) => {
}).then((data) => data.json())
}
+const fetchUserInLists = ({ id, credentials }) => {
+ const url = MASTODON_USER_IN_LISTS(id)
+ return fetch(url, {
+ headers: authHeaders(credentials)
+ }).then((data) => data.json())
+}
+
const pinOwnStatus = ({ id, credentials }) => {
return promisedRequest({ url: MASTODON_PIN_OWN_STATUS(id), credentials, method: 'POST' })
.then((data) => parseStatus(data))
@@ -428,14 +436,14 @@ const createList = ({ title, credentials }) => {
}).then((data) => data.json())
}
-const getList = ({ id, credentials }) => {
- const url = MASTODON_LIST_URL(id)
+const getList = ({ listId, credentials }) => {
+ const url = MASTODON_LIST_URL(listId)
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json())
}
-const updateList = ({ id, title, credentials }) => {
- const url = MASTODON_LIST_URL(id)
+const updateList = ({ listId, title, credentials }) => {
+ const url = MASTODON_LIST_URL(listId)
const headers = authHeaders(credentials)
headers['Content-Type'] = 'application/json'
@@ -446,15 +454,15 @@ const updateList = ({ id, title, credentials }) => {
})
}
-const getListAccounts = ({ id, credentials }) => {
- const url = MASTODON_LIST_ACCOUNTS_URL(id)
+const getListAccounts = ({ listId, credentials }) => {
+ const url = MASTODON_LIST_ACCOUNTS_URL(listId)
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json())
.then((data) => data.map(({ id }) => id))
}
-const addAccountsToList = ({ id, accountIds, credentials }) => {
- const url = MASTODON_LIST_ACCOUNTS_URL(id)
+const addAccountsToList = ({ listId, accountIds, credentials }) => {
+ const url = MASTODON_LIST_ACCOUNTS_URL(listId)
const headers = authHeaders(credentials)
headers['Content-Type'] = 'application/json'
@@ -465,8 +473,8 @@ const addAccountsToList = ({ id, accountIds, credentials }) => {
})
}
-const removeAccountsFromList = ({ id, accountIds, credentials }) => {
- const url = MASTODON_LIST_ACCOUNTS_URL(id)
+const removeAccountsFromList = ({ listId, accountIds, credentials }) => {
+ const url = MASTODON_LIST_ACCOUNTS_URL(listId)
const headers = authHeaders(credentials)
headers['Content-Type'] = 'application/json'
@@ -477,8 +485,8 @@ const removeAccountsFromList = ({ id, accountIds, credentials }) => {
})
}
-const deleteList = ({ id, credentials }) => {
- const url = MASTODON_LIST_URL(id)
+const deleteList = ({ listId, credentials }) => {
+ const url = MASTODON_LIST_URL(listId)
return fetch(url, {
method: 'DELETE',
headers: authHeaders(credentials)
@@ -1584,7 +1592,8 @@ const apiService = {
sendChatMessage,
readChat,
deleteChatMessage,
- setReportState
+ setReportState,
+ fetchUserInLists
}
export default apiService
diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js
@@ -43,11 +43,13 @@ export const parseUser = (data) => {
// case for users in "mentions" property for statuses in MastoAPI
const mastoShort = masto && !Object.prototype.hasOwnProperty.call(data, 'avatar')
+ output.inLists = null
output.id = String(data.id)
output._original = data // used for server-side settings
if (masto) {
output.screen_name = data.acct
+ output.fqn = data.fqn
output.statusnet_profile_url = data.url
// There's nothing else to get
@@ -214,12 +216,14 @@ export const parseUser = (data) => {
output.screen_name_ui = output.screen_name
if (output.screen_name && output.screen_name.includes('@')) {
const parts = output.screen_name.split('@')
- let unicodeDomain = punycode.toUnicode(parts[1])
+ const unicodeDomain = punycode.toUnicode(parts[1])
if (unicodeDomain !== parts[1]) {
// Add some identifier so users can potentially spot spoofing attempts:
// lain.com and xn--lin-6cd.com would appear identical otherwise.
- unicodeDomain = '🌏' + unicodeDomain
+ output.screen_name_ui_contains_non_ascii = true
output.screen_name_ui = [parts[0], unicodeDomain].join('@')
+ } else {
+ output.screen_name_ui_contains_non_ascii = false
}
}
diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js
@@ -17,11 +17,6 @@ const webpackConfig = merge(baseConfig, {
rules: utils.styleLoaders()
},
devtool: 'inline-source-map',
- // vue: {
- // loaders: {
- // js: 'isparta'
- // }
- // },
plugins: [
new webpack.DefinePlugin({
'process.env': require('../../config/test.env')
@@ -37,22 +32,6 @@ const webpackConfig = merge(baseConfig, {
// no need for app entry during tests
delete webpackConfig.entry
-// make sure isparta loader is applied before eslint
-// webpackConfig.module.preLoaders = webpackConfig.module.preLoaders || []
-// webpackConfig.module.preLoaders.unshift({
-// test: /\.js$/,
-// loader: 'isparta',
-// include: path.resolve(projectRoot, 'src')
-// })
-
-// // only apply babel for test files when using isparta
-// webpackConfig.module.loaders.some(function (loader, i) {
-// if (loader.loader === 'babel') {
-// loader.include = path.resolve(projectRoot, 'test/unit')
-// return true
-// }
-// })
-
module.exports = function (config) {
config.set({
// to run in additional browsers:
diff --git a/test/unit/specs/modules/lists.spec.js b/test/unit/specs/modules/lists.spec.js
@@ -17,13 +17,13 @@ describe('The lists module', () => {
const list = { id: '1', title: 'testList' }
const modList = { id: '1', title: 'anotherTestTitle' }
- mutations.setList(state, list)
- expect(state.allListsObject[list.id]).to.eql({ title: list.title })
+ mutations.setList(state, { listId: list.id, title: list.title })
+ expect(state.allListsObject[list.id]).to.eql({ title: list.title, accountIds: [] })
expect(state.allLists).to.have.length(1)
expect(state.allLists[0]).to.eql(list)
- mutations.setList(state, modList)
- expect(state.allListsObject[modList.id]).to.eql({ title: modList.title })
+ mutations.setList(state, { listId: modList.id, title: modList.title })
+ expect(state.allListsObject[modList.id]).to.eql({ title: modList.title, accountIds: [] })
expect(state.allLists).to.have.length(1)
expect(state.allLists[0]).to.eql(modList)
})
@@ -33,10 +33,10 @@ describe('The lists module', () => {
const list = { id: '1', accountIds: ['1', '2', '3'] }
const modList = { id: '1', accountIds: ['3', '4', '5'] }
- mutations.setListAccounts(state, list)
+ mutations.setListAccounts(state, { listId: list.id, accountIds: list.accountIds })
expect(state.allListsObject[list.id]).to.eql({ accountIds: list.accountIds })
- mutations.setListAccounts(state, modList)
+ mutations.setListAccounts(state, { listId: modList.id, accountIds: modList.accountIds })
expect(state.allListsObject[modList.id]).to.eql({ accountIds: modList.accountIds })
})
@@ -47,9 +47,9 @@ describe('The lists module', () => {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
}
- const id = '1'
+ const listId = '1'
- mutations.deleteList(state, { id })
+ mutations.deleteList(state, { listId })
expect(state.allLists).to.have.length(0)
expect(state.allListsObject).to.eql({})
})
diff --git a/test/unit/specs/modules/serverSideStorage.spec.js b/test/unit/specs/modules/serverSideStorage.spec.js
@@ -4,9 +4,11 @@ import {
VERSION,
COMMAND_TRIM_FLAGS,
COMMAND_TRIM_FLAGS_AND_RESET,
+ _moveItemInArray,
_getRecentData,
_getAllFlags,
_mergeFlags,
+ _mergePrefs,
_resetFlags,
mutations,
defaultState,
@@ -28,6 +30,7 @@ describe('The serverSideStorage module', () => {
expect(state.cache._version).to.eql(VERSION)
expect(state.cache._timestamp).to.be.a('number')
expect(state.cache.flagStorage).to.eql(defaultState.flagStorage)
+ expect(state.cache.prefsStorage).to.eql(defaultState.prefsStorage)
})
it('should initialize storage with proper flags for new users if none present', () => {
@@ -36,6 +39,7 @@ describe('The serverSideStorage module', () => {
expect(state.cache._version).to.eql(VERSION)
expect(state.cache._timestamp).to.be.a('number')
expect(state.cache.flagStorage).to.eql(newUserFlags)
+ expect(state.cache.prefsStorage).to.eql(defaultState.prefsStorage)
})
it('should merge flags even if remote timestamp is older', () => {
@@ -57,6 +61,9 @@ describe('The serverSideStorage module', () => {
flagStorage: {
...defaultState.flagStorage,
updateCounter: 1
+ },
+ prefsStorage: {
+ ...defaultState.prefsStorage
}
}
}
@@ -99,9 +106,62 @@ describe('The serverSideStorage module', () => {
expect(state.cache.flagStorage).to.eql(defaultState.flagStorage)
})
})
+ describe('setPreference', () => {
+ const { setPreference, updateCache, addCollectionPreference, removeCollectionPreference } = mutations
+
+ it('should set preference and update journal log accordingly', () => {
+ const state = cloneDeep(defaultState)
+ setPreference(state, { path: 'simple.testing', value: 1 })
+ expect(state.prefsStorage.simple.testing).to.eql(1)
+ expect(state.prefsStorage._journal.length).to.eql(1)
+ expect(state.prefsStorage._journal[0]).to.eql({
+ path: 'simple.testing',
+ operation: 'set',
+ args: [1],
+ // should have A timestamp, we don't really care what it is
+ timestamp: state.prefsStorage._journal[0].timestamp
+ })
+ })
+
+ it('should keep journal to a minimum', () => {
+ const state = cloneDeep(defaultState)
+ setPreference(state, { path: 'simple.testing', value: 1 })
+ setPreference(state, { path: 'simple.testing', value: 2 })
+ addCollectionPreference(state, { path: 'collections.testing', value: 2 })
+ removeCollectionPreference(state, { path: 'collections.testing', value: 2 })
+ updateCache(state, { username: 'test' })
+ expect(state.prefsStorage.simple.testing).to.eql(2)
+ expect(state.prefsStorage.collections.testing).to.eql([])
+ expect(state.prefsStorage._journal.length).to.eql(2)
+ expect(state.prefsStorage._journal[0]).to.eql({
+ path: 'simple.testing',
+ operation: 'set',
+ args: [2],
+ // should have A timestamp, we don't really care what it is
+ timestamp: state.prefsStorage._journal[0].timestamp
+ })
+ expect(state.prefsStorage._journal[1]).to.eql({
+ path: 'collections.testing',
+ operation: 'removeFromCollection',
+ args: [2],
+ // should have A timestamp, we don't really care what it is
+ timestamp: state.prefsStorage._journal[1].timestamp
+ })
+ })
+ })
})
describe('helper functions', () => {
+ describe('_moveItemInArray', () => {
+ it('should move item according to movement value', () => {
+ expect(_moveItemInArray([1, 2, 3, 4], 4, -1)).to.eql([1, 2, 4, 3])
+ expect(_moveItemInArray([1, 2, 3, 4], 1, 2)).to.eql([2, 3, 1, 4])
+ })
+ it('should clamp movement to within array', () => {
+ expect(_moveItemInArray([1, 2, 3, 4], 4, -10)).to.eql([4, 1, 2, 3])
+ expect(_moveItemInArray([1, 2, 3, 4], 3, 99)).to.eql([1, 2, 4, 3])
+ })
+ })
describe('_getRecentData', () => {
it('should handle nulls correctly', () => {
expect(_getRecentData(null, null)).to.eql({ recent: null, stale: null, needUpload: true })
@@ -157,6 +217,94 @@ describe('The serverSideStorage module', () => {
})
})
+ describe('_mergePrefs', () => {
+ it('should prefer recent and apply journal to it', () => {
+ expect(
+ _mergePrefs(
+ // RECENT
+ {
+ simple: { a: 1, b: 0, c: true },
+ _journal: [
+ { path: 'simple.b', operation: 'set', args: [0], timestamp: 2 },
+ { path: 'simple.c', operation: 'set', args: [true], timestamp: 4 }
+ ]
+ },
+ // STALE
+ {
+ simple: { a: 1, b: 1, c: false },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: [1], timestamp: 1 },
+ { path: 'simple.b', operation: 'set', args: [1], timestamp: 3 }
+ ]
+ }
+ )
+ ).to.eql({
+ simple: { a: 1, b: 1, c: true },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: [1], timestamp: 1 },
+ { path: 'simple.b', operation: 'set', args: [1], timestamp: 3 },
+ { path: 'simple.c', operation: 'set', args: [true], timestamp: 4 }
+ ]
+ })
+ })
+
+ it('should allow setting falsy values', () => {
+ expect(
+ _mergePrefs(
+ // RECENT
+ {
+ simple: { a: 1, b: 0, c: false },
+ _journal: [
+ { path: 'simple.b', operation: 'set', args: [0], timestamp: 2 },
+ { path: 'simple.c', operation: 'set', args: [false], timestamp: 4 }
+ ]
+ },
+ // STALE
+ {
+ simple: { a: 0, b: 0, c: true },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: [0], timestamp: 1 },
+ { path: 'simple.b', operation: 'set', args: [0], timestamp: 3 }
+ ]
+ }
+ )
+ ).to.eql({
+ simple: { a: 0, b: 0, c: false },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: [0], timestamp: 1 },
+ { path: 'simple.b', operation: 'set', args: [0], timestamp: 3 },
+ { path: 'simple.c', operation: 'set', args: [false], timestamp: 4 }
+ ]
+ })
+ })
+
+ it('should work with strings', () => {
+ expect(
+ _mergePrefs(
+ // RECENT
+ {
+ simple: { a: 'foo' },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: ['foo'], timestamp: 2 }
+ ]
+ },
+ // STALE
+ {
+ simple: { a: 'bar' },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: ['bar'], timestamp: 4 }
+ ]
+ }
+ )
+ ).to.eql({
+ simple: { a: 'bar' },
+ _journal: [
+ { path: 'simple.a', operation: 'set', args: ['bar'], timestamp: 4 }
+ ]
+ })
+ })
+ })
+
describe('_resetFlags', () => {
it('should reset all known flags to 0 when reset flag is set to > 0 and < 9000', () => {
const totalFlags = { a: 0, b: 3, reset: 1 }
diff --git a/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js b/test/unit/specs/services/entity_normalizer/entity_normalizer.spec.js
@@ -269,7 +269,8 @@ describe('API Entities normalizer', () => {
it('converts IDN to unicode and marks it as internatonal', () => {
const user = makeMockUserMasto({ acct: 'lain@xn--lin-6cd.com' })
- expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@🌏lаin.com')
+ expect(parseUser(user)).to.have.property('screen_name_ui').that.equal('lain@lаin.com')
+ expect(parseUser(user)).to.have.property('screen_name_ui_contains_non_ascii').that.equal(true)
})
})
diff --git a/yarn.lock b/yarn.lock
@@ -40,7 +40,28 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d"
integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==
-"@babel/core@7.18.10", "@babel/core@^7.12.3":
+"@babel/core@7.18.13":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.13.tgz#9be8c44512751b05094a4d3ab05fc53a47ce00ac"
+ integrity sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==
+ dependencies:
+ "@ampproject/remapping" "^2.1.0"
+ "@babel/code-frame" "^7.18.6"
+ "@babel/generator" "^7.18.13"
+ "@babel/helper-compilation-targets" "^7.18.9"
+ "@babel/helper-module-transforms" "^7.18.9"
+ "@babel/helpers" "^7.18.9"
+ "@babel/parser" "^7.18.13"
+ "@babel/template" "^7.18.10"
+ "@babel/traverse" "^7.18.13"
+ "@babel/types" "^7.18.13"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.1"
+ semver "^6.3.0"
+
+"@babel/core@^7.12.3":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8"
integrity sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==
@@ -109,6 +130,15 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
+"@babel/generator@^7.18.13":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.13.tgz#59550cbb9ae79b8def15587bdfbaa388c4abf212"
+ integrity sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==
+ dependencies:
+ "@babel/types" "^7.18.13"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ jsesc "^2.5.1"
+
"@babel/generator@^7.18.6", "@babel/generator@^7.18.7":
version "7.18.7"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.7.tgz#2aa78da3c05aadfc82dbac16c99552fc802284bd"
@@ -543,6 +573,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.10.tgz#94b5f8522356e69e8277276adf67ed280c90ecc1"
integrity sha512-TYk3OA0HKL6qNryUayb5UUEhM/rkOQozIBEA5ITXh5DWrSp0TlUQXMyZmnWxG/DizSWBeeQ0Zbc5z8UGaaqoeg==
+"@babel/parser@^7.18.13":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.13.tgz#5b2dd21cae4a2c5145f1fbd8ca103f9313d3b7e4"
+ integrity sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==
+
"@babel/parser@^7.18.6", "@babel/parser@^7.18.8":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.8.tgz#822146080ac9c62dac0823bb3489622e0bc1cbdf"
@@ -1266,6 +1301,22 @@
debug "^4.1.0"
globals "^11.1.0"
+"@babel/traverse@^7.18.13":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.13.tgz#5ab59ef51a997b3f10c4587d648b9696b6cb1a68"
+ integrity sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==
+ dependencies:
+ "@babel/code-frame" "^7.18.6"
+ "@babel/generator" "^7.18.13"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-function-name" "^7.18.9"
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/parser" "^7.18.13"
+ "@babel/types" "^7.18.13"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
"@babel/traverse@^7.18.6", "@babel/traverse@^7.18.8":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.8.tgz#f095e62ab46abf1da35e5a2011f43aee72d8d5b0"
@@ -1323,6 +1374,15 @@
"@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0"
+"@babel/types@^7.18.13":
+ version "7.18.13"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.13.tgz#30aeb9e514f4100f7c1cb6e5ba472b30e48f519a"
+ integrity sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==
+ dependencies:
+ "@babel/helper-string-parser" "^7.18.10"
+ "@babel/helper-validator-identifier" "^7.18.6"
+ to-fast-properties "^2.0.0"
+
"@babel/types@^7.18.6", "@babel/types@^7.18.7", "@babel/types@^7.18.8":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.8.tgz#c5af199951bf41ba4a6a9a6d0d8ad722b30cd42f"
@@ -1358,14 +1418,14 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
-"@eslint/eslintrc@^1.3.0":
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f"
- integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==
+"@eslint/eslintrc@^1.3.1":
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.1.tgz#de0807bfeffc37b964a7d0400e0c348ce5a2543d"
+ integrity sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
- espree "^9.3.2"
+ espree "^9.4.0"
globals "^13.15.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
@@ -1418,6 +1478,11 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz#316b0a63b91c10e53f242efb4ace5c3b34e8728d"
integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==
+"@humanwhocodes/module-importer@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
+ integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
+
"@humanwhocodes/object-schema@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
@@ -1764,10 +1829,10 @@
resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
-"@vue/babel-helper-vue-jsx-merge-props@1.2.1":
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz#31624a7a505fb14da1d58023725a4c5f270e6a81"
- integrity sha512-QOi5OW45e2R20VygMSNhyQHvpdUwQZqGPc748JLGCYEy+yp8fNFNdbNIGAgZmi9e+2JHPd6i6idRuqivyicIkA==
+"@vue/babel-helper-vue-jsx-merge-props@1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz#8d53a1e21347db8edbe54d339902583176de09f2"
+ integrity sha512-JkqXfCkUDp4PIlFdDQ0TdXoIejMtTHP67/pvxlgeY+u5k3LEdKuWZ3LK6xkxo52uDoABIVyRwqVkfLQJhk7VBA==
"@vue/babel-helper-vue-transform-on@^1.0.2":
version "1.0.2"
@@ -2037,10 +2102,6 @@
version "4.2.2"
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
-abbrev@1, abbrev@1.0.x:
- version "1.0.9"
- resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135"
-
accepts@~1.3.4:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@@ -2130,10 +2191,6 @@ ajv@^8.0.0, ajv@^8.0.1, ajv@^8.8.0:
require-from-string "^2.0.2"
uri-js "^4.2.2"
-amdefine@>=0.0.4:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
-
ansi-align@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59"
@@ -2202,10 +2259,6 @@ ansi-styles@^4.1.0:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
-ansi-styles@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178"
-
ansi-to-html@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.7.2.tgz#a92c149e4184b571eb29a0135ca001a8e2d710cb"
@@ -2221,12 +2274,6 @@ anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
-argparse@^1.0.7:
- version "1.0.10"
- resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
- dependencies:
- sprintf-js "~1.0.2"
-
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
@@ -2276,16 +2323,6 @@ astral-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
-async@1.x:
- version "1.5.2"
- resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-
-async@^2.5.0:
- version "2.6.1"
- resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
- dependencies:
- lodash "^4.17.10"
-
async@^3.2.3:
version "3.2.4"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c"
@@ -2329,58 +2366,6 @@ axios@^0.27.2:
follow-redirects "^1.14.9"
form-data "^4.0.0"
-babel-code-frame@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
- dependencies:
- chalk "^1.1.3"
- esutils "^2.0.2"
- js-tokens "^3.0.2"
-
-babel-core@^6.1.4, babel-core@^6.26.0:
- version "6.26.3"
- resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207"
- dependencies:
- babel-code-frame "^6.26.0"
- babel-generator "^6.26.0"
- babel-helpers "^6.24.1"
- babel-messages "^6.23.0"
- babel-register "^6.26.0"
- babel-runtime "^6.26.0"
- babel-template "^6.26.0"
- babel-traverse "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- convert-source-map "^1.5.1"
- debug "^2.6.9"
- json5 "^0.5.1"
- lodash "^4.17.4"
- minimatch "^3.0.4"
- path-is-absolute "^1.0.1"
- private "^0.1.8"
- slash "^1.0.0"
- source-map "^0.5.7"
-
-babel-generator@^6.26.0:
- version "6.26.1"
- resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90"
- dependencies:
- babel-messages "^6.23.0"
- babel-runtime "^6.26.0"
- babel-types "^6.26.0"
- detect-indent "^4.0.0"
- jsesc "^1.3.0"
- lodash "^4.17.4"
- source-map "^0.5.7"
- trim-right "^1.0.1"
-
-babel-helpers@^6.24.1:
- version "6.24.1"
- resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2"
- dependencies:
- babel-runtime "^6.22.0"
- babel-template "^6.24.1"
-
babel-loader@8.2.5:
version "8.2.5"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e"
@@ -2391,12 +2376,6 @@ babel-loader@8.2.5:
make-dir "^3.1.0"
schema-utils "^2.6.5"
-babel-messages@^6.23.0:
- version "6.23.0"
- resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
- dependencies:
- babel-runtime "^6.22.0"
-
babel-plugin-dynamic-import-node@^2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
@@ -2439,62 +2418,6 @@ babel-plugin-polyfill-regenerator@^0.4.0:
dependencies:
"@babel/helper-define-polyfill-provider" "^0.3.2"
-babel-register@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071"
- dependencies:
- babel-core "^6.26.0"
- babel-runtime "^6.26.0"
- core-js "^2.5.0"
- home-or-tmp "^2.0.0"
- lodash "^4.17.4"
- mkdirp "^0.5.1"
- source-map-support "^0.4.15"
-
-babel-runtime@^6.22.0, babel-runtime@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
- dependencies:
- core-js "^2.4.0"
- regenerator-runtime "^0.11.0"
-
-babel-template@^6.24.1, babel-template@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02"
- dependencies:
- babel-runtime "^6.26.0"
- babel-traverse "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- lodash "^4.17.4"
-
-babel-traverse@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee"
- dependencies:
- babel-code-frame "^6.26.0"
- babel-messages "^6.23.0"
- babel-runtime "^6.26.0"
- babel-types "^6.26.0"
- babylon "^6.18.0"
- debug "^2.6.8"
- globals "^9.18.0"
- invariant "^2.2.2"
- lodash "^4.17.4"
-
-babel-types@^6.26.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497"
- dependencies:
- babel-runtime "^6.26.0"
- esutils "^2.0.2"
- lodash "^4.17.4"
- to-fast-properties "^1.0.3"
-
-babylon@^6.18.0:
- version "6.18.0"
- resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
-
bail@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776"
@@ -2804,7 +2727,7 @@ chai@4.3.6:
pathval "^1.1.1"
type-detect "^4.0.5"
-chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
+chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
dependencies:
@@ -2838,14 +2761,6 @@ chalk@^4.1.0:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
-chalk@~0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f"
- dependencies:
- ansi-styles "~1.0.0"
- has-color "~0.1.0"
- strip-ansi "~0.1.0"
-
character-entities-legacy@^1.0.0:
version "1.1.4"
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1"
@@ -3046,10 +2961,6 @@ commander@^8.3.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
-commander@~2.17.1:
- version "2.17.1"
- resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
-
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
@@ -3089,12 +3000,6 @@ content-type@~1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
-convert-source-map@^1.5.1:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
- dependencies:
- safe-buffer "~5.1.1"
-
convert-source-map@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
@@ -3144,10 +3049,6 @@ core-js-compat@^3.22.1:
browserslist "^4.21.1"
semver "7.0.0"
-core-js@^2.4.0, core-js@^2.5.0:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.2.tgz#267988d7268323b349e20b4588211655f0e83944"
-
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -3325,7 +3226,7 @@ de-indent@^1.0.2:
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
-debug@2.6.9, debug@^2.6.8, debug@^2.6.9:
+debug@2.6.9, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
dependencies:
@@ -3408,7 +3309,7 @@ deep-eql@^3.0.1:
dependencies:
type-detect "^4.0.0"
-deep-is@^0.1.3, deep-is@~0.1.3:
+deep-is@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -3463,12 +3364,6 @@ destroy@1.2.0:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
-detect-indent@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
- dependencies:
- repeating "^2.0.0"
-
di@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
@@ -3793,28 +3688,6 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
-escodegen@1.8.x:
- version "1.8.1"
- resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018"
- dependencies:
- esprima "^2.7.1"
- estraverse "^1.9.1"
- esutils "^2.0.2"
- optionator "^0.8.1"
- optionalDependencies:
- source-map "~0.2.0"
-
-escodegen@^1.6.1:
- version "1.11.0"
- resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.11.0.tgz#b27a9389481d5bfd5bec76f7bb1eb3f8f4556589"
- dependencies:
- esprima "^3.1.3"
- estraverse "^4.2.0"
- esutils "^2.0.2"
- optionator "^0.8.1"
- optionalDependencies:
- source-map "~0.6.1"
-
eslint-config-standard@17.0.0:
version "17.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz#fd5b6cf1dcf6ba8d29f200c461de2e19069888cf"
@@ -3888,10 +3761,10 @@ eslint-plugin-n@15.2.5:
resolve "^1.22.1"
semver "^7.3.7"
-eslint-plugin-promise@6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz#017652c07c9816413a41e11c30adc42c3d55ff18"
- integrity sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==
+eslint-plugin-promise@6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-6.0.1.tgz#a8cddf96a67c4059bdabf4d724a29572188ae423"
+ integrity sha512-uM4Tgo5u3UWQiroOyDEsYcVMOo7re3zmno0IZmB5auxoaQNIceAbXEkSt8RNrKtaYehARHG06pYK6K1JhtP0Zw==
eslint-plugin-vue@9.4.0:
version "9.4.0"
@@ -3962,14 +3835,15 @@ eslint-webpack-plugin@3.2.0:
normalize-path "^3.0.0"
schema-utils "^4.0.0"
-eslint@8.22.0:
- version "8.22.0"
- resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.22.0.tgz#78fcb044196dfa7eef30a9d65944f6f980402c48"
- integrity sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA==
+eslint@8.23.0:
+ version "8.23.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.23.0.tgz#a184918d288820179c6041bb3ddcc99ce6eea040"
+ integrity sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==
dependencies:
- "@eslint/eslintrc" "^1.3.0"
+ "@eslint/eslintrc" "^1.3.1"
"@humanwhocodes/config-array" "^0.10.4"
"@humanwhocodes/gitignore-to-minimatch" "^1.0.2"
+ "@humanwhocodes/module-importer" "^1.0.1"
ajv "^6.10.0"
chalk "^4.0.0"
cross-spawn "^7.0.2"
@@ -3979,7 +3853,7 @@ eslint@8.22.0:
eslint-scope "^7.1.1"
eslint-utils "^3.0.0"
eslint-visitor-keys "^3.3.0"
- espree "^9.3.3"
+ espree "^9.4.0"
esquery "^1.4.0"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
@@ -4005,7 +3879,6 @@ eslint@8.22.0:
strip-ansi "^6.0.1"
strip-json-comments "^3.1.0"
text-table "^0.2.0"
- v8-compile-cache "^2.0.3"
espree@^6.0.0:
version "6.2.1"
@@ -4016,7 +3889,7 @@ espree@^6.0.0:
acorn-jsx "^5.2.0"
eslint-visitor-keys "^1.1.0"
-espree@^9.3.1, espree@^9.3.2:
+espree@^9.3.1:
version "9.3.2"
resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596"
integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==
@@ -4025,27 +3898,15 @@ espree@^9.3.1, espree@^9.3.2:
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.3.0"
-espree@^9.3.3:
- version "9.3.3"
- resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.3.tgz#2dd37c4162bb05f433ad3c1a52ddf8a49dc08e9d"
- integrity sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==
+espree@^9.4.0:
+ version "9.4.0"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a"
+ integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==
dependencies:
acorn "^8.8.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.3.0"
-esprima@2.7.x, esprima@^2.7.1:
- version "2.7.3"
- resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
-
-esprima@^3.1.3:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
-
-esprima@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
-
esquery@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
@@ -4060,11 +3921,7 @@ esrecurse@^4.3.0:
dependencies:
estraverse "^5.2.0"
-estraverse@^1.9.1:
- version "1.9.3"
- resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44"
-
-estraverse@^4.1.1, estraverse@^4.2.0:
+estraverse@^4.1.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
@@ -4193,7 +4050,7 @@ fast-json-stable-stringify@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
-fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4:
+fast-levenshtein@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
@@ -4493,16 +4350,6 @@ glob@7.2.0, glob@^7.1.7:
once "^1.3.0"
path-is-absolute "^1.0.0"
-glob@^5.0.15:
- version "5.0.15"
- resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
- dependencies:
- inflight "^1.0.4"
- inherits "2"
- minimatch "2 || 3"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
-
glob@^7.0.0, glob@^7.1.1, glob@^7.1.3:
version "7.1.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
@@ -4553,10 +4400,6 @@ globals@^13.15.0:
dependencies:
type-fest "^0.20.2"
-globals@^9.18.0:
- version "9.18.0"
- resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
-
globby@^11.0.1:
version "11.0.1"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357"
@@ -4633,16 +4476,6 @@ growl@1.10.5:
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e"
integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==
-handlebars@^4.0.1:
- version "4.0.12"
- resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5"
- dependencies:
- async "^2.5.0"
- optimist "^0.6.1"
- source-map "^0.6.1"
- optionalDependencies:
- uglify-js "^3.1.4"
-
hard-rejection@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883"
@@ -4659,14 +4492,6 @@ has-bigints@^1.0.1:
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
-has-color@~0.1.0:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f"
-
-has-flag@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa"
-
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@@ -4712,13 +4537,6 @@ he@1.2.0, he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
-home-or-tmp@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
- dependencies:
- os-homedir "^1.0.0"
- os-tmpdir "^1.0.1"
-
hosted-git-info@^2.1.4:
version "2.7.1"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047"
@@ -4942,12 +4760,6 @@ interpret@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
-invariant@^2.2.2:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
- dependencies:
- loose-envify "^1.0.0"
-
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
@@ -5049,12 +4861,6 @@ is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
-is-finite@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
- dependencies:
- number-is-nan "^1.0.0"
-
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
@@ -5234,27 +5040,6 @@ isobject@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
-isparta-loader@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/isparta-loader/-/isparta-loader-2.0.0.tgz#4425f496c93f765bbceb4dd938576da307566ed1"
- integrity sha1-RCX0lsk/dlu8603ZOFdtowdWbtE=
- dependencies:
- isparta "4.x.x"
-
-isparta@4.x.x:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/isparta/-/isparta-4.1.1.tgz#c92e49672946914ec5407c801160f3374e0b7cb4"
- dependencies:
- babel-core "^6.1.4"
- escodegen "^1.6.1"
- esprima "^4.0.0"
- istanbul "0.4.5"
- mkdirp "^0.5.0"
- nomnomnomnom "^2.0.0"
- object-assign "^4.0.1"
- source-map "^0.5.0"
- which "^1.0.9"
-
istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
@@ -5297,25 +5082,6 @@ istanbul-reports@^3.0.5:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
-istanbul@0.4.5:
- version "0.4.5"
- resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b"
- dependencies:
- abbrev "1.0.x"
- async "1.x"
- escodegen "1.8.x"
- esprima "2.7.x"
- glob "^5.0.15"
- handlebars "^4.0.1"
- js-yaml "3.x"
- mkdirp "0.5.x"
- nopt "3.x"
- once "1.x"
- resolve "1.1.x"
- supports-color "^3.1.0"
- which "^1.1.1"
- wordwrap "^1.0.0"
-
jake@^10.8.5:
version "10.8.5"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
@@ -5349,21 +5115,10 @@ js-cookie@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"
-
js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
-js-yaml@3.x:
- version "3.12.1"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600"
- dependencies:
- argparse "^1.0.7"
- esprima "^4.0.0"
-
js-yaml@4.1.0, js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
@@ -5371,10 +5126,6 @@ js-yaml@4.1.0, js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
-jsesc@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
-
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
@@ -5411,10 +5162,6 @@ json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
-json5@^0.5.1:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
-
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
@@ -5584,13 +5331,6 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
-levn@~0.3.0:
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
- dependencies:
- prelude-ls "~1.1.2"
- type-check "~0.3.2"
-
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
@@ -5892,7 +5632,7 @@ lodash@4.17.21, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
-lodash@^4.17.10, lodash@^4.17.4:
+lodash@^4.17.10:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
@@ -5942,12 +5682,6 @@ longest-streak@^2.0.0:
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4"
integrity sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==
-loose-envify@^1.0.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
- dependencies:
- js-tokens "^3.0.0 || ^4.0.0"
-
loupe@2.3.4, loupe@^2.3.1:
version "2.3.4"
resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3"
@@ -6161,7 +5895,7 @@ mini-css-extract-plugin@2.6.1:
dependencies:
schema-utils "^4.0.0"
-"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.4:
+minimatch@3.0.4, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
dependencies:
@@ -6204,7 +5938,7 @@ minimist-options@4.1.0:
is-plain-obj "^1.1.0"
kind-of "^6.0.3"
-minimist@0.0.8, minimist@~0.0.1:
+minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
@@ -6222,7 +5956,7 @@ minimist@^1.2.5:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
-mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1:
+mkdirp@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
dependencies:
@@ -6407,19 +6141,6 @@ node-releases@^2.0.5, node-releases@^2.0.6:
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==
-nomnomnomnom@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/nomnomnomnom/-/nomnomnomnom-2.0.1.tgz#b2239f031c8d04da67e32836e1e3199e12f7a8e2"
- dependencies:
- chalk "~0.4.0"
- underscore "~1.6.0"
-
-nopt@3.x:
- version "3.0.6"
- resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9"
- dependencies:
- abbrev "1"
-
normalize-package-data@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -6469,11 +6190,7 @@ num2fraction@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
-number-is-nan@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
-
-object-assign@^4, object-assign@^4.0.1:
+object-assign@^4:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -6528,7 +6245,7 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
-once@1.x, once@^1.3.0, once@^1.3.1, once@^1.4.0:
+once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
dependencies:
@@ -6563,24 +6280,6 @@ opn@5.5.0:
dependencies:
is-wsl "^1.1.0"
-optimist@^0.6.1:
- version "0.6.1"
- resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
- dependencies:
- minimist "~0.0.1"
- wordwrap "~0.0.2"
-
-optionator@^0.8.1:
- version "0.8.2"
- resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
- dependencies:
- deep-is "~0.1.3"
- fast-levenshtein "~2.0.4"
- levn "~0.3.0"
- prelude-ls "~1.1.2"
- type-check "~0.3.2"
- wordwrap "~1.0.0"
-
optionator@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
@@ -6618,14 +6317,6 @@ ora@5.4.1:
strip-ansi "^6.0.0"
wcwidth "^1.0.1"
-os-homedir@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
-
-os-tmpdir@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
-
p-limit@^1.1.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8"
@@ -6763,7 +6454,7 @@ path-exists@^4.0.0:
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
-path-is-absolute@^1.0.0, path-is-absolute@^1.0.1:
+path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@@ -7219,10 +6910,6 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
-prelude-ls@~1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
-
pretty-error@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6"
@@ -7231,10 +6918,6 @@ pretty-error@^4.0.0:
lodash "^4.17.20"
renderkid "^3.0.0"
-private@^0.1.8:
- version "0.1.8"
- resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
-
process-nextick-args@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
@@ -7437,10 +7120,6 @@ regenerate@^1.4.2:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
-regenerator-runtime@^0.11.0:
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
-
regenerator-runtime@^0.13.4:
version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
@@ -7537,12 +7216,6 @@ repeat-string@^1.0.0:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
-repeating@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
- dependencies:
- is-finite "^1.0.0"
-
replace-ext@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
@@ -7579,7 +7252,7 @@ resolve-from@^5.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
-resolve@1.1.x, resolve@^1.1.6:
+resolve@^1.1.6:
version "1.1.7"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
@@ -7723,11 +7396,6 @@ selenium-webdriver@^4.3.1:
version "5.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
-semver@5.7.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"
@@ -7740,18 +7408,18 @@ semver@7.3.5:
dependencies:
lru-cache "^6.0.0"
-semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
- version "6.3.0"
- resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
- integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
-
-semver@^7.0.0, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7:
+semver@7.3.7, semver@^7.0.0, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7:
version "7.3.7"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
dependencies:
lru-cache "^6.0.0"
+semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+ integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
send@0.18.0:
version "0.18.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
@@ -7849,10 +7517,10 @@ signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
-sinon-chai@2.14.0:
- version "2.14.0"
- resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-2.14.0.tgz#da7dd4cc83cd6a260b67cca0f7a9fdae26a1205d"
- integrity sha512-9stIF1utB0ywNHNT7RgiXbdmen8QDCRsrTjw+G9TgKt1Yexjiv8TOWZ6WHsTPz57Yky3DIswZvEqX8fpuHNDtQ==
+sinon-chai@3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/sinon-chai/-/sinon-chai-3.7.0.tgz#cfb7dec1c50990ed18c153f1840721cf13139783"
+ integrity sha512-mf5NURdUaSdnatJx3uhoBOrY9dtL19fiOtAdT1Azxg3+lNJFiuN0uzaU3xX1LeAfL17kHQhTAJgpsfhbMJMY2g==
sinon@2.4.1:
version "2.4.1"
@@ -7868,10 +7536,6 @@ sinon@2.4.1:
text-encoding "0.6.4"
type-detect "^4.0.0"
-slash@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
-
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
@@ -7922,12 +7586,6 @@ socket.io@^4.4.1:
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
-source-map-support@^0.4.15:
- version "0.4.18"
- resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"
- dependencies:
- source-map "^0.5.6"
-
source-map-support@^0.5.16:
version "0.5.16"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042"
@@ -7944,20 +7602,14 @@ source-map-support@~0.5.20:
buffer-from "^1.0.0"
source-map "^0.6.0"
-source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
+source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
-source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7:
+source-map@^0.5.0:
version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
-source-map@~0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d"
- dependencies:
- amdefine ">=0.0.4"
-
sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
@@ -7990,10 +7642,6 @@ specificity@^0.4.1:
resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019"
integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==
-sprintf-js@~1.0.2:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
-
stable@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
@@ -8105,10 +7753,6 @@ strip-ansi@^6.0.0:
dependencies:
ansi-regex "^5.0.0"
-strip-ansi@~0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991"
-
strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
@@ -8230,12 +7874,6 @@ supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
-supports-color@^3.1.0:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5"
- dependencies:
- has-flag "^1.0.0"
-
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -8337,10 +7975,6 @@ tmp@^0.2.1:
dependencies:
rimraf "^3.0.0"
-to-fast-properties@^1.0.3:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
-
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
@@ -8362,10 +7996,6 @@ trim-newlines@^3.0.0:
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.0.tgz#79726304a6a898aa8373427298d54c2ee8b1cb30"
integrity sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==
-trim-right@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
-
trough@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
@@ -8393,12 +8023,6 @@ type-check@^0.4.0, type-check@~0.4.0:
dependencies:
prelude-ls "^1.2.1"
-type-check@~0.3.2:
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
- dependencies:
- prelude-ls "~1.1.2"
-
type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
@@ -8448,13 +8072,6 @@ ua-parser-js@^0.7.30:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6"
integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==
-uglify-js@^3.1.4:
- version "3.4.9"
- resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3"
- dependencies:
- commander "~2.17.1"
- source-map "~0.6.1"
-
unbox-primitive@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
@@ -8465,10 +8082,6 @@ unbox-primitive@^1.0.1:
has-symbols "^1.0.2"
which-boxed-primitive "^1.0.2"
-underscore@~1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
-
unicode-canonical-property-names-ecmascript@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
@@ -8587,7 +8200,7 @@ uuid@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
-v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0:
+v8-compile-cache@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
@@ -8663,10 +8276,10 @@ vue-loader@17.0.0:
hash-sum "^2.0.0"
loader-utils "^2.0.0"
-vue-router@4.1.3:
- version "4.1.3"
- resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.1.3.tgz#f8dc7931a2253cc5aa9b740f8b98969d08ca283c"
- integrity sha512-XvK81bcYglKiayT7/vYAg/f36ExPC4t90R/HIpzrZ5x+17BOWptXLCrEPufGgZeuq68ww4ekSIMBZY1qdUdfjA==
+vue-router@4.1.5:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.1.5.tgz#256f597e3f5a281a23352a6193aa6e342c8d9f9a"
+ integrity sha512-IsvoF5D2GQ/EGTs/Th4NQms9gd2NSqV+yylxIyp/OYp8xOwxmU8Kj/74E9DTSYAyH5LX7idVUngN3JSj1X4xcQ==
dependencies:
"@vue/devtools-api" "^6.1.4"
@@ -8678,10 +8291,10 @@ vue-style-loader@4.1.3:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
-vue-template-compiler@2.7.9:
- version "2.7.9"
- resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.9.tgz#ffbeb1769ae6af65cd405a6513df6b1e20e33616"
- integrity sha512-NPJxt6OjVlzmkixYg0SdIN2Lw/rMryQskObY89uAMcM9flS/HrmLK5LaN1ReBTuhBgnYuagZZEkSS6FESATQUQ==
+vue-template-compiler@2.7.10:
+ version "2.7.10"
+ resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.7.10.tgz#9e20f35b2fdccacacf732dd7dedb49bf65f4556b"
+ integrity sha512-QO+8R9YRq1Gudm8ZMdo/lImZLJVUIAM8c07Vp84ojdDAf8HmPJc7XB556PcXV218k2AkKznsRz6xB5uOjAC4EQ==
dependencies:
de-indent "^1.0.2"
he "^1.2.0"
@@ -8822,7 +8435,7 @@ which@2.0.2, which@^2.0.1:
dependencies:
isexe "^2.0.0"
-which@^1.0.9, which@^1.1.1, which@^1.3.1:
+which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
dependencies:
@@ -8840,14 +8453,6 @@ word-wrap@^1.2.3:
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
-wordwrap@^1.0.0, wordwrap@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
-
-wordwrap@~0.0.2:
- version "0.0.3"
- resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
-
workerpool@6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"