Merge remote-tracking branch 'origin/develop' into shigusegubu-vue3

* origin/develop: (22 commits)
  Fix lint stuff
  Lists implementation: make route naming consistent
  Lists implementation: tests, linter fix
  Lists implementation
  Update babel monorepo to v7.18.10
  clean up leftover
  Use empty array for emji instead
  Add default array for RichContent emoji
  Use RichContent component for Reports
  Use Select component
  Make linter happy
  Fix up and code review
  Fix setting report state, add proper error handling
  remove logs
  Fix report modal not working, add include_types
  remove mock data
  add proper state switcher
  wip
  separated component
  somewhat workign version still with fixture
  ...
This commit is contained in:
Henry Jameson 2022-08-10 00:56:45 +03:00
commit fa4c827df6
50 changed files with 1659 additions and 135 deletions

View file

@ -46,9 +46,9 @@
"vuex": "4.0.2"
},
"devDependencies": {
"@babel/core": "7.18.9",
"@babel/plugin-transform-runtime": "7.18.9",
"@babel/preset-env": "7.18.9",
"@babel/core": "7.18.10",
"@babel/plugin-transform-runtime": "7.18.10",
"@babel/preset-env": "7.18.10",
"@babel/register": "7.18.9",
"@babel/eslint-parser": "7.18.9",
"@intlify/vue-i18n-loader": "^5.0.0",

View file

@ -20,6 +20,9 @@ import ShoutPanel from 'components/shout_panel/shout_panel.vue'
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
import About from 'components/about/about.vue'
import RemoteUserResolver from 'components/remote_user_resolver/remote_user_resolver.vue'
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'
export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => {
@ -72,7 +75,10 @@ export default (store) => {
{ name: 'search', path: '/search', component: Search, props: (route) => ({ query: route.query.query }) },
{ name: 'who-to-follow', path: '/who-to-follow', component: WhoToFollow, beforeEnter: validateAuthenticatedRoute },
{ name: 'about', path: '/about', component: About },
{ name: 'user-profile', path: '/:_(users)?/:name', component: UserProfile }
{ name: 'user-profile', path: '/:_(users)?/: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 }
]
if (store.state.instance.pleromaChatMessagesAvailable) {

View file

@ -5,6 +5,8 @@ const tabModeDict = {
mentions: ['mention'],
'likes+repeats': ['repeat', 'like'],
follows: ['follow'],
reactions: ['pleroma:emoji_reaction'],
reports: ['pleroma:report'],
moves: ['move']
}
@ -12,7 +14,8 @@ const Interactions = {
data () {
return {
allowFollowingMove: this.$store.state.users.currentUser.allow_following_move,
filterMode: tabModeDict.mentions
filterMode: tabModeDict.mentions,
canSeeReports: ['moderator', 'admin'].includes(this.$store.state.users.currentUser.role)
}
},
methods: {

View file

@ -21,6 +21,15 @@
key="follows"
:label="$t('interactions.follows')"
/>
<span
key="reactions"
:label="$t('interactions.emoji_reactions')"
/>
<span
v-if="canSeeReports"
key="reports"
:label="$t('interactions.reports')"
/>
<span
v-if="!allowFollowingMove"
key="moves"

View file

@ -0,0 +1,32 @@
import ListsCard from '../lists_card/lists_card.vue'
import ListsNew from '../lists_new/lists_new.vue'
const Lists = {
data () {
return {
isNew: false
}
},
components: {
ListsCard,
ListsNew
},
created () {
this.$store.dispatch('startFetchingLists')
},
computed: {
lists () {
return this.$store.state.lists.allLists
}
},
methods: {
cancelNewList () {
this.isNew = false
},
newList () {
this.isNew = true
}
}
}
export default Lists

View file

@ -0,0 +1,31 @@
<template>
<div v-if="isNew">
<ListsNew @cancel="cancelNewList" />
</div>
<div
v-else
class="settings panel panel-default"
>
<div class="panel-heading">
<div class="title">
{{ $t('lists.lists') }}
</div>
<button
class="button-default"
@click="newList"
>
{{ $t("lists.new") }}
</button>
</div>
<div class="panel-body">
<ListsCard
v-for="list in lists.slice().reverse()"
:key="list"
:list="list"
class="list-item"
/>
</div>
</div>
</template>
<script src="./lists.js"></script>

View file

@ -0,0 +1,16 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEllipsisH
} from '@fortawesome/free-solid-svg-icons'
library.add(
faEllipsisH
)
const ListsCard = {
props: [
'list'
]
}
export default ListsCard

View file

@ -0,0 +1,51 @@
<template>
<div class="list-card">
<router-link
:to="{ name: 'lists-timeline', params: { id: list.id } }"
class="list-name"
>
{{ list.title }}
</router-link>
<router-link
:to="{ name: 'lists-edit', params: { id: list.id } }"
class="button-list-edit"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="ellipsis-h"
/>
</router-link>
</div>
</template>
<script src="./lists_card.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.list-card {
display: flex;
}
.list-name,
.button-list-edit {
margin: 0;
padding: 1em;
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);
}
}
.list-name {
flex-grow: 1;
}
</style>

View file

@ -0,0 +1,91 @@
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 UserAvatar from '../user_avatar/user_avatar.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: []
}
},
created () {
this.$store.dispatch('fetchList', { id: this.id })
.then(() => { this.title = this.findListTitle(this.id) })
this.$store.dispatch('fetchListAccounts', { id: this.id })
.then(() => {
this.selectedUserIds = this.findListAccounts(this.id)
this.selectedUserIds.forEach(userId => {
this.$store.dispatch('fetchUserIfMissing', userId)
})
})
},
computed: {
id () {
return this.$route.params.id
},
users () {
return this.userIds.map(userId => this.findUser(userId))
},
selectedUsers () {
return this.selectedUserIds.map(userId => this.findUser(userId)).filter(user => user)
},
...mapState({
currentUser: state => state.users.currentUser
}),
...mapGetters(['findUser', 'findListTitle', 'findListAccounts'])
},
methods: {
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
},
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 } })
},
deleteList () {
this.$store.dispatch('deleteList', { id: this.id })
this.$router.push({ name: 'lists' })
}
}
}
export default ListsNew

View file

@ -0,0 +1,108 @@
<template>
<div class="panel-default panel list-edit">
<div
ref="header"
class="panel-heading"
>
<button
class="button-unstyled go-back-button"
@click="$router.back"
>
<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 class="member-list">
<div
v-for="user in users"
:key="user.id"
class="member"
>
<BasicUserCard
:user="user"
:class="isSelected(user) ? 'selected' : ''"
@click.capture.prevent="selectUser(user)"
/>
</div>
</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>
<script src="./lists_edit.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.list-edit {
.input-wrap {
display: flex;
margin: 0.7em 0.5em 0.7em 0.5em;
input {
width: 100%;
}
}
.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>

View file

@ -0,0 +1,33 @@
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 ListsMenuContent = {
created () {
this.$store.dispatch('startFetchingLists')
},
computed: {
...mapState({
lists: state => state.lists.allLists,
currentUser: state => state.users.currentUser,
privateMode: state => state.instance.private,
federating: state => state.instance.federating
})
}
}
export default ListsMenuContent

View file

@ -0,0 +1,17 @@
<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>
</ul>
</template>
<script src="./lists_menu_content.js"></script>

View file

@ -0,0 +1,79 @@
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

View file

@ -0,0 +1,95 @@
<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>

View file

@ -0,0 +1,36 @@
import Timeline from '../timeline/timeline.vue'
const ListsTimeline = {
data () {
return {
listId: null
}
},
components: {
Timeline
},
computed: {
timeline () { return this.$store.state.statuses.timelines.list }
},
watch: {
$route: function (route) {
if (route.name === 'lists-timeline' && route.params.id !== this.listId) {
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('startFetchingTimeline', { timeline: 'list', listId: this.listId })
}
}
},
created () {
this.listId = this.$route.params.id
this.$store.dispatch('fetchList', { id: this.listId })
this.$store.dispatch('startFetchingTimeline', { timeline: 'list', listId: this.listId })
},
unmounted () {
this.$store.dispatch('stopFetchingTimeline', 'list')
this.$store.commit('clearTimeline', { timeline: 'list' })
}
}
export default ListsTimeline

View file

@ -0,0 +1,10 @@
<template>
<Timeline
title="list.name"
:timeline="timeline"
:list-id="listId"
timeline-name="list"
/>
</template>
<script src="./lists_timeline.js"></script>

View file

@ -0,0 +1,46 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faSearch,
faChevronLeft
} from '@fortawesome/free-solid-svg-icons'
import { debounce } from 'lodash'
import Checkbox from '../checkbox/checkbox.vue'
library.add(
faSearch,
faChevronLeft
)
const ListsUserSearch = {
components: {
Checkbox
},
data () {
return {
loading: false,
query: '',
followingOnly: true
}
},
methods: {
onInput: debounce(function () {
this.search(this.query)
}, 2000),
search (query) {
if (!query) {
this.loading = false
return
}
this.loading = true
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))
})
}
}
}
export default ListsUserSearch

View file

@ -0,0 +1,45 @@
<template>
<div>
<div class="input-wrap">
<div class="input-search">
<FAIcon
class="search-icon fa-scale-110 fa-old-padding"
icon="search"
/>
</div>
<input
ref="search"
v-model="query"
:placeholder="$t('lists.search')"
@input="onInput"
>
</div>
<div class="input-wrap">
<Checkbox
v-model="followingOnly"
@change="onInput"
>
{{ $t('lists.following_only') }}
</Checkbox>
</div>
</div>
</template>
<script src="./lists_user_search.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.input-wrap {
display: flex;
margin: 0.7em 0.5em 0.7em 0.5em;
input {
width: 100%;
}
}
.search-icon {
margin-right: 0.3em;
}
</style>

View file

@ -1,4 +1,5 @@
import TimelineMenuContent from '../timeline_menu/timeline_menu_content.vue'
import ListsMenuContent from '../lists_menu/lists_menu_content.vue'
import { mapState, mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
@ -12,7 +13,8 @@ import {
faComments,
faBell,
faInfoCircle,
faStream
faStream,
faList
} from '@fortawesome/free-solid-svg-icons'
library.add(
@ -25,7 +27,8 @@ library.add(
faComments,
faBell,
faInfoCircle,
faStream
faStream,
faList
)
const NavPanel = {
@ -35,19 +38,27 @@ const NavPanel = {
}
},
components: {
TimelineMenuContent
TimelineMenuContent,
ListsMenuContent
},
data () {
return {
showTimelines: false
showTimelines: false,
showLists: false
}
},
methods: {
toggleTimelines () {
this.showTimelines = !this.showTimelines
},
toggleLists () {
this.showLists = !this.showLists
}
},
computed: {
listsNavigation () {
return this.$store.getters.mergedConfig.listsNavigation
},
...mapState({
currentUser: state => state.users.currentUser,
followRequestCount: state => state.api.followRequests.length,

View file

@ -25,6 +25,51 @@
<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'"
/>
</button>
<div
v-show="showLists"
class="timelines-background"
>
<ListsMenuContent class="timelines" />
</div>
</li>
<li v-if="currentUser && !listsNavigation">
<router-link
: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"

View file

@ -4,6 +4,7 @@ import Status from '../status/status.vue'
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 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'
@ -47,6 +48,7 @@ const Notification = {
UserCard,
Timeago,
Status,
Report,
RichContent,
UserPopover
},

View file

@ -121,6 +121,9 @@
</i18n-t>
</small>
</span>
<span v-if="notification.type === 'pleroma:report'">
<small>{{ $t('notifications.submitted_report') }}</small>
</span>
<span v-if="notification.type === 'poll'">
<FAIcon
class="type-icon"
@ -211,6 +214,10 @@
@{{ notification.target.screen_name_ui }}
</router-link>
</div>
<Report
v-else-if="notification.type === 'pleroma:report'"
:report-id="notification.report.id"
/>
<template v-else>
<StatusContent
class="faint"

View file

@ -59,8 +59,10 @@
height: 32px;
}
--link: var(--faintLink);
--text: var(--faint);
.faint {
--link: var(--faintLink);
--text: var(--faint);
}
}
.follow-request-accept {

View file

@ -0,0 +1,34 @@
import Select from '../select/select.vue'
import StatusContent from '../status_content/status_content.vue'
import Timeago from '../timeago/timeago.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const Report = {
props: [
'reportId'
],
components: {
Select,
StatusContent,
Timeago
},
computed: {
report () {
return this.$store.state.reports.reports[this.reportId] || {}
},
state: {
get: function () { return this.report.state },
set: function (val) { this.setReportState(val) }
}
},
methods: {
generateUserProfileLink (user) {
return generateProfileLink(user.id, user.screen_name, this.$store.state.instance.restrictedNicknames)
},
setReportState (state) {
return this.$store.dispatch('setReportState', { id: this.report.id, state })
}
}
}
export default Report

View file

@ -0,0 +1,43 @@
@import '../../_variables.scss';
.Report {
.report-content {
margin: 0.5em 0 1em;
}
.report-state {
margin: 0.5em 0 1em;
}
.reported-status {
border: 1px solid $fallback--faint;
border-color: var(--faint, $fallback--faint);
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
color: $fallback--text;
color: var(--text, $fallback--text);
display: block;
padding: 0.5em;
margin: 0.5em 0;
.status-content {
pointer-events: none;
}
.reported-status-heading {
display: flex;
width: 100%;
justify-content: space-between;
margin-bottom: 0.2em;
}
.reported-status-name {
font-weight: bold;
}
}
.note {
width: 100%;
margin-bottom: 0.5em;
}
}

View file

@ -0,0 +1,74 @@
<template>
<div class="Report">
<div class="reported-user">
<span>{{ $t('report.reported_user') }}</span>
<router-link :to="generateUserProfileLink(report.acct)">
@{{ report.acct.screen_name }}
</router-link>
</div>
<div class="reporter">
<span>{{ $t('report.reporter') }}</span>
<router-link :to="generateUserProfileLink(report.actor)">
@{{ report.actor.screen_name }}
</router-link>
</div>
<div class="report-state">
<span>{{ $t('report.state') }}</span>
<Select
:id="report-state"
v-model="state"
class="form-control"
>
<option
v-for="state in ['open', 'closed', 'resolved']"
:key="state"
:value="state"
>
{{ $t('report.state_' + state) }}
</option>
</Select>
</div>
<RichContent
class="report-content"
:html="report.content"
:emoji="[]"
/>
<div v-if="report.statuses.length">
<small>{{ $t('report.reported_statuses') }}</small>
<router-link
v-for="status in report.statuses"
:key="status.id"
:to="{ name: 'conversation', params: { id: status.id } }"
class="reported-status"
>
<div class="reported-status-heading">
<span class="reported-status-name">{{ status.user.name }}</span>
<Timeago
:time="status.created_at"
:auto-update="240"
class="faint"
/>
</div>
<status-content :status="status" />
</router-link>
</div>
<div v-if="report.notes.length">
<small>{{ $t('report.notes') }}</small>
<div
v-for="note in report.notes"
:key="note.id"
class="note"
>
<span>{{ note.content }}</span>
<Timeago
:time="note.created_at"
:auto-update="240"
class="faint"
/>
</div>
</div>
</div>
</template>
<script src="./report.js"></script>
<style src="./report.scss" lang="scss"></style>

View file

@ -124,6 +124,53 @@
{{ $t('settings.hide_shoutbox') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="listsNavigation">
{{ $t('settings.lists_navigation') }}
</BooleanSetting>
</li>
<li>
<h3>{{ $t('settings.columns') }}</h3>
</li>
<li>
<BooleanSetting path="disableStickyHeaders">
{{ $t('settings.disable_sticky_headers') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="showScrollbars">
{{ $t('settings.show_scrollbars') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="sidebarRight">
{{ $t('settings.right_sidebar') }}
</BooleanSetting>
</li>
<li>
<ChoiceSetting
v-if="user"
id="thirdColumnMode"
path="thirdColumnMode"
:options="thirdColumnModeOptions"
>
{{ $t('settings.third_column_mode') }}
</ChoiceSetting>
</li>
<li v-if="expertLevel > 0">
{{ $t('settings.column_sizes') }}
<div class="column-settings">
<SizeSetting
v-for="column in columns"
:key="column"
:path="column + 'ColumnWidth'"
:units="horizontalUnits"
expert="1"
>
{{ $t('settings.column_sizes_' + column) }}
</SizeSetting>
</div>
</li>
</ul>
</div>
<div class="setting-item">

View file

@ -14,7 +14,8 @@ import {
faSearch,
faTachometerAlt,
faCog,
faInfoCircle
faInfoCircle,
faList
} from '@fortawesome/free-solid-svg-icons'
library.add(
@ -28,7 +29,8 @@ library.add(
faSearch,
faTachometerAlt,
faCog,
faInfoCircle
faInfoCircle,
faList
)
const SideDrawer = {

View file

@ -55,6 +55,18 @@
/> {{ $t("nav.timelines") }}
</router-link>
</li>
<li
v-if="currentUser"
@click="toggleDrawer"
>
<router-link :to="{ name: 'lists' }">
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="list"
/> {{ $t("nav.lists") }}
</router-link>
</li>
<li
v-if="currentUser && pleromaChatMessagesAvailable"
@click="toggleDrawer"

View file

@ -19,6 +19,7 @@ const Timeline = {
'timelineName',
'title',
'userId',
'listId',
'tag',
'embedded',
'count',
@ -103,6 +104,7 @@ const Timeline = {
timeline: this.timelineName,
showImmediately,
userId: this.userId,
listId: this.listId,
tag: this.tag
})
},
@ -158,6 +160,7 @@ const Timeline = {
older: true,
showImmediately: true,
userId: this.userId,
listId: this.listId,
tag: this.tag
}).then(({ statuses }) => {
if (statuses && statuses.length === 0) {

View file

@ -58,6 +58,9 @@ const TimelineMenu = {
if (route === 'tag-timeline') {
return '#' + this.$route.params.tag
}
if (route === 'lists-timeline') {
return this.$store.getters.findListTitle(this.$route.params.id)
}
const i18nkey = timelineNames()[this.$route.name]
return i18nkey ? this.$t(i18nkey) : route
}

View file

@ -1,4 +1,3 @@
import Status from '../status/status.vue'
import List from '../list/list.vue'
import Checkbox from '../checkbox/checkbox.vue'
@ -21,14 +20,17 @@ const UserReportingModal = {
}
},
computed: {
reportModal () {
return this.$store.state.reports.reportModal
},
isLoggedIn () {
return !!this.$store.state.users.currentUser
},
isOpen () {
return this.isLoggedIn && this.$store.state.reports.modalActivated
return this.isLoggedIn && this.reportModal.activated
},
userId () {
return this.$store.state.reports.userId
return this.reportModal.userId
},
user () {
return this.$store.getters.findUser(this.userId)
@ -37,10 +39,10 @@ const UserReportingModal = {
return !this.user.is_local && this.user.screen_name.substr(this.user.screen_name.indexOf('@') + 1)
},
statuses () {
return this.$store.state.reports.statuses
return this.reportModal.statuses
},
preTickedIds () {
return this.$store.state.reports.preTickedIds
return this.reportModal.preTickedIds
}
},
watch: {

View file

@ -66,6 +66,7 @@
"more": "More",
"loading": "Loading…",
"generic_error": "An error occured",
"generic_error_message": "An error occured: {0}",
"error_retry": "Please try again",
"retry": "Try again",
"optional": "optional",
@ -147,7 +148,8 @@
"who_to_follow": "Who to follow",
"preferences": "Preferences",
"timelines": "Timelines",
"chats": "Chats"
"chats": "Chats",
"lists": "Lists"
},
"notifications": {
"broken_favorite": "Unknown status, searching for it…",
@ -162,6 +164,7 @@
"no_more_notifications": "No more notifications",
"migrated_to": "migrated to",
"reacted_with": "reacted with {0}",
"submitted_report": "submitted a report",
"poll_ended": "poll has ended"
},
"polls": {
@ -197,6 +200,8 @@
"interactions": {
"favs_repeats": "Repeats and favorites",
"follows": "New follows",
"emoji_reactions": "Emoji Reactions",
"reports": "Reports",
"moves": "User migrates",
"load_older": "Load older interactions"
},
@ -265,6 +270,16 @@
"searching_for": "Searching for",
"error": "Not found."
},
"report": {
"reporter": "Reporter:",
"reported_user": "Reported user:",
"reported_statuses": "Reported statuses:",
"notes": "Notes:",
"state": "State:",
"state_open": "Open",
"state_closed": "Closed",
"state_resolved": "Resolved"
},
"selectable_list": {
"select_all": "Select all"
},
@ -299,6 +314,7 @@
"desc": "To enable two-factor authentication, enter the code from your two-factor app:"
}
},
"lists_navigation": "Show lists in navigation",
"allow_following_move": "Allow auto-follow when following account moves",
"attachmentRadius": "Attachments",
"attachments": "Attachments",
@ -954,6 +970,16 @@
"error_sending_message": "Something went wrong when sending the message.",
"empty_chat_list_placeholder": "You don't have any chats yet. Start a new chat!"
},
"lists": {
"lists": "Lists",
"new": "New List",
"title": "List title",
"search": "Search users",
"create": "Create",
"save": "Save changes",
"delete": "Delete list",
"following_only": "Limit to Following"
},
"file_type": {
"audio": "Audio",
"video": "Video",

View file

@ -744,6 +744,8 @@
"favs_repeats": "Herhalingen en favorieten",
"follows": "Nieuwe gevolgden",
"moves": "Gebruikermigraties",
"emoji_reactions": "Emoji Reacties",
"reports": "Rapportages",
"load_older": "Oudere interacties laden"
},
"remote_user_resolver": {
@ -751,6 +753,17 @@
"error": "Niet gevonden.",
"remote_user_resolver": "Externe gebruikers-zoeker"
},
"report": {
"reporter": "Reporteerder:",
"reported_user": "Gerapporteerde gebruiker:",
"reported_statuses": "Gerapporteerde statussen:",
"notes": "Notas:",
"state": "Status:",
"state_open": "Open",
"state_closed": "Gesloten",
"state_resolved": "Opgelost"
},
"selectable_list": {
"select_all": "Alles selecteren"
},

View file

@ -6,6 +6,7 @@ import './lib/event_target_polyfill.js'
import interfaceModule from './modules/interface.js'
import instanceModule from './modules/instance.js'
import statusesModule from './modules/statuses.js'
import listsModule from './modules/lists.js'
import usersModule from './modules/users.js'
import apiModule from './modules/api.js'
import configModule from './modules/config.js'
@ -72,6 +73,7 @@ const persistedStateOptions = {
// TODO refactor users/statuses modules, they depend on each other
users: usersModule,
statuses: statusesModule,
lists: listsModule,
api: apiModule,
config: configModule,
serverSideConfig: serverSideConfigModule,

View file

@ -191,12 +191,13 @@ const api = {
startFetchingTimeline (store, {
timeline = 'friends',
tag = false,
userId = false
userId = false,
listId = false
}) {
if (store.state.fetchers[timeline]) return
const fetcher = store.state.backendInteractor.startFetchingTimeline({
timeline, store, userId, tag
timeline, store, userId, listId, tag
})
store.commit('addFetcher', { fetcherName: timeline, fetcher })
},
@ -248,6 +249,18 @@ const api = {
store.commit('setFollowRequests', requests)
},
// Lists
startFetchingLists (store) {
if (store.state.fetchers.lists) return
const fetcher = store.state.backendInteractor.startFetchingLists({ store })
store.commit('addFetcher', { fetcherName: 'lists', fetcher })
},
stopFetchingLists (store) {
const fetcher = store.state.fetchers.lists
if (!fetcher) return
store.commit('removeFetcher', { fetcherName: 'lists', fetcher })
},
// Pleroma websocket
setWsToken (store, token) {
store.commit('setWsToken', token)

View file

@ -59,6 +59,7 @@ export const defaultState = {
moves: true,
emojiReactions: true,
followRequest: true,
reports: true,
chatMention: true,
polls: true
},
@ -83,6 +84,10 @@ export const defaultState = {
showScrollbars: false,
userPopoverZoom: false,
userPopoverOverlay: true,
sidebarColumnWidth: '25rem',
contentColumnWidth: '45rem',
notifsColumnWidth: '25rem',
listsNavigation: false,
greentext: undefined, // instance default
useAtIcon: undefined, // instance default
mentionLinkDisplay: undefined, // instance default

94
src/modules/lists.js Normal file
View file

@ -0,0 +1,94 @@
import { remove, find } from 'lodash'
export const defaultState = {
allLists: [],
allListsObject: {}
}
export const mutations = {
setLists (state, value) {
state.allLists = value
},
setList (state, { id, title }) {
if (!state.allListsObject[id]) {
state.allListsObject[id] = {}
}
state.allListsObject[id].title = title
if (!find(state.allLists, { id })) {
state.allLists.push({ id, title })
} else {
find(state.allLists, { id }).title = title
}
},
setListAccounts (state, { id, accountIds }) {
if (!state.allListsObject[id]) {
state.allListsObject[id] = {}
}
state.allListsObject[id].accountIds = accountIds
},
deleteList (state, { id }) {
delete state.allListsObject[id]
remove(state.allLists, list => list.id === id)
}
}
const actions = {
setLists ({ commit }, value) {
commit('setLists', value)
},
createList ({ rootState, commit }, { title }) {
return rootState.api.backendInteractor.createList({ title })
.then((list) => {
commit('setList', { id: 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 }))
},
fetchListAccounts ({ rootState, commit }, { id }) {
return rootState.api.backendInteractor.getListAccounts({ id })
.then((accountIds) => commit('setListAccounts', { id, accountIds }))
},
setList ({ rootState, commit }, { id, title }) {
rootState.api.backendInteractor.updateList({ id, title })
commit('setList', { id, title })
},
setListAccounts ({ rootState, commit }, { id, accountIds }) {
const saved = rootState.lists.allListsObject[id].accountIds || []
const added = accountIds.filter(id => !saved.includes(id))
const removed = saved.filter(id => !accountIds.includes(id))
commit('setListAccounts', { id, accountIds })
if (added.length > 0) {
rootState.api.backendInteractor.addAccountsToList({ id, accountIds: added })
}
if (removed.length > 0) {
rootState.api.backendInteractor.removeAccountsFromList({ id, accountIds: removed })
}
},
deleteList ({ rootState, commit }, { id }) {
rootState.api.backendInteractor.deleteList({ id })
commit('deleteList', { id })
}
}
export const getters = {
findListTitle: state => id => {
if (!state.allListsObject[id]) return
return state.allListsObject[id].title
},
findListAccounts: state => id => {
return [...state.allListsObject[id].accountIds]
}
}
const lists = {
state: defaultState,
mutations,
actions,
getters
}
export default lists

View file

@ -2,20 +2,29 @@ import filter from 'lodash/filter'
const reports = {
state: {
userId: null,
statuses: [],
preTickedIds: [],
modalActivated: false
reportModal: {
userId: null,
statuses: [],
preTickedIds: [],
activated: false
},
reports: {}
},
mutations: {
openUserReportingModal (state, { userId, statuses, preTickedIds }) {
state.userId = userId
state.statuses = statuses
state.preTickedIds = preTickedIds
state.modalActivated = true
state.reportModal.userId = userId
state.reportModal.statuses = statuses
state.reportModal.preTickedIds = preTickedIds
state.reportModal.activated = true
},
closeUserReportingModal (state) {
state.modalActivated = false
state.reportModal.activated = false
},
setReportState (reportsState, { id, state }) {
reportsState.reports[id].state = state
},
addReport (state, report) {
state.reports[report.id] = report
}
},
actions: {
@ -31,6 +40,23 @@ const reports = {
},
closeUserReportingModal ({ commit }) {
commit('closeUserReportingModal')
},
setReportState ({ commit, dispatch, rootState }, { id, state }) {
const oldState = rootState.reports.reports[id].state
commit('setReportState', { id, state })
rootState.api.backendInteractor.setReportState({ id, state }).catch(e => {
console.error('Failed to set report state', e)
dispatch('pushGlobalNotice', {
level: 'error',
messageKey: 'general.generic_error_message',
messageArgs: [e.message],
timeout: 5000
})
commit('setReportState', { id, state: oldState })
})
},
addReport ({ commit }, report) {
commit('addReport', report)
}
}
}

View file

@ -62,7 +62,8 @@ export const defaultState = () => ({
friends: emptyTl(),
tag: emptyTl(),
dms: emptyTl(),
bookmarks: emptyTl()
bookmarks: emptyTl(),
list: emptyTl()
}
})
@ -336,6 +337,10 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
notification.status = notification.status && addStatusToGlobalStorage(state, notification.status).item
}
if (notification.type === 'pleroma:report') {
dispatch('addReport', notification.report)
}
if (notification.type === 'pleroma:emoji_reaction') {
dispatch('fetchEmojiReactionsBy', notification.status.id)
}

View file

@ -52,6 +52,9 @@ const MASTODON_STATUS_CONTEXT_URL = id => `/api/v1/statuses/${id}/context`
const MASTODON_USER_URL = '/api/v1/accounts'
const MASTODON_USER_RELATIONSHIPS_URL = '/api/v1/accounts/relationships'
const MASTODON_USER_TIMELINE_URL = id => `/api/v1/accounts/${id}/statuses`
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`
const MASTODON_TAG_TIMELINE_URL = tag => `/api/v1/timelines/tag/${tag}`
const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks'
const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/'
@ -79,6 +82,7 @@ const MASTODON_UNMUTE_CONVERSATION = id => `/api/v1/statuses/${id}/unmute`
const MASTODON_SEARCH_2 = '/api/v2/search'
const MASTODON_USER_SEARCH_URL = '/api/v1/accounts/search'
const MASTODON_DOMAIN_BLOCKS_URL = '/api/v1/domain_blocks'
const MASTODON_LISTS_URL = '/api/v1/lists'
const MASTODON_STREAMING = '/api/v1/streaming'
const MASTODON_KNOWN_DOMAIN_LIST_URL = '/api/v1/instance/peers'
const PLEROMA_EMOJI_REACTIONS_URL = id => `/api/v1/pleroma/statuses/${id}/reactions`
@ -89,6 +93,7 @@ const PLEROMA_CHAT_URL = id => `/api/v1/pleroma/chats/by-account-id/${id}`
const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages`
const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read`
const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
const PLEROMA_ADMIN_REPORTS = '/api/pleroma/admin/reports'
const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
const oldfetch = window.fetch
@ -385,6 +390,81 @@ const fetchFollowRequests = ({ credentials }) => {
.then((data) => data.map(parseUser))
}
const fetchLists = ({ credentials }) => {
const url = MASTODON_LISTS_URL
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json())
}
const createList = ({ title, credentials }) => {
const url = MASTODON_LISTS_URL
const headers = authHeaders(credentials)
headers['Content-Type'] = 'application/json'
return fetch(url, {
headers,
method: 'POST',
body: JSON.stringify({ title })
}).then((data) => data.json())
}
const getList = ({ id, credentials }) => {
const url = MASTODON_LIST_URL(id)
return fetch(url, { headers: authHeaders(credentials) })
.then((data) => data.json())
}
const updateList = ({ id, title, credentials }) => {
const url = MASTODON_LIST_URL(id)
const headers = authHeaders(credentials)
headers['Content-Type'] = 'application/json'
return fetch(url, {
headers,
method: 'PUT',
body: JSON.stringify({ title })
})
}
const getListAccounts = ({ id, credentials }) => {
const url = MASTODON_LIST_ACCOUNTS_URL(id)
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 headers = authHeaders(credentials)
headers['Content-Type'] = 'application/json'
return fetch(url, {
headers,
method: 'POST',
body: JSON.stringify({ account_ids: accountIds })
})
}
const removeAccountsFromList = ({ id, accountIds, credentials }) => {
const url = MASTODON_LIST_ACCOUNTS_URL(id)
const headers = authHeaders(credentials)
headers['Content-Type'] = 'application/json'
return fetch(url, {
headers,
method: 'DELETE',
body: JSON.stringify({ account_ids: accountIds })
})
}
const deleteList = ({ id, credentials }) => {
const url = MASTODON_LIST_URL(id)
return fetch(url, {
method: 'DELETE',
headers: authHeaders(credentials)
})
}
const fetchConversation = ({ id, credentials }) => {
const urlContext = MASTODON_STATUS_CONTEXT_URL(id)
return fetch(urlContext, { headers: authHeaders(credentials) })
@ -506,9 +586,11 @@ const fetchTimeline = ({
since = false,
until = false,
userId = false,
listId = false,
tag = false,
withMuted = false,
replyVisibility = 'all'
replyVisibility = 'all',
includeTypes = []
}) => {
const timelineUrls = {
public: MASTODON_PUBLIC_TIMELINE,
@ -518,6 +600,7 @@ const fetchTimeline = ({
publicAndExternal: MASTODON_PUBLIC_TIMELINE,
user: MASTODON_USER_TIMELINE_URL,
media: MASTODON_USER_TIMELINE_URL,
list: MASTODON_LIST_TIMELINE_URL,
favorites: MASTODON_USER_FAVORITES_TIMELINE_URL,
tag: MASTODON_TAG_TIMELINE_URL,
bookmarks: MASTODON_BOOKMARK_TIMELINE_URL
@ -531,6 +614,10 @@ const fetchTimeline = ({
url = url(userId)
}
if (timeline === 'list') {
url = url(listId)
}
if (since) {
params.push(['since_id', since])
}
@ -555,6 +642,11 @@ const fetchTimeline = ({
if (replyVisibility !== 'all') {
params.push(['reply_visibility', replyVisibility])
}
if (includeTypes.length > 0) {
includeTypes.forEach(type => {
params.push(['include_types[]', type])
})
}
params.push(['limit', 20])
@ -1339,6 +1431,38 @@ const deleteChatMessage = ({ chatId, messageId, credentials }) => {
})
}
const setReportState = ({ id, state, credentials }) => {
// TODO: Can't use promisedRequest because on OK this does not return json
// See https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1322
return fetch(PLEROMA_ADMIN_REPORTS, {
headers: {
...authHeaders(credentials),
Accept: 'application/json',
'Content-Type': 'application/json'
},
method: 'PATCH',
body: JSON.stringify({
reports: [{
id,
state
}]
})
})
.then(data => {
if (data.status >= 500) {
throw Error(data.statusText)
} else if (data.status >= 400) {
return data.json()
}
return data
})
.then(data => {
if (data.errors) {
throw Error(data.errors[0].message)
}
})
}
const apiService = {
verifyCredentials,
fetchTimeline,
@ -1405,6 +1529,14 @@ const apiService = {
addBackup,
listBackups,
fetchFollowRequests,
fetchLists,
createList,
getList,
updateList,
getListAccounts,
addAccountsToList,
removeAccountsFromList,
deleteList,
approveUser,
denyUser,
suggestions,
@ -1430,7 +1562,8 @@ const apiService = {
chatMessages,
sendChatMessage,
readChat,
deleteChatMessage
deleteChatMessage,
setReportState
}
export default apiService

View file

@ -2,10 +2,11 @@ import apiService, { getMastodonSocketURI, ProcessedWS } from '../api/api.servic
import timelineFetcher from '../timeline_fetcher/timeline_fetcher.service.js'
import notificationsFetcher from '../notifications_fetcher/notifications_fetcher.service.js'
import followRequestFetcher from '../../services/follow_request_fetcher/follow_request_fetcher.service'
import listsFetcher from '../../services/lists_fetcher/lists_fetcher.service.js'
const backendInteractorService = credentials => ({
startFetchingTimeline ({ timeline, store, userId = false, tag }) {
return timelineFetcher.startFetching({ timeline, store, credentials, userId, tag })
startFetchingTimeline ({ timeline, store, userId = false, listId = false, tag }) {
return timelineFetcher.startFetching({ timeline, store, credentials, userId, listId, tag })
},
fetchTimeline (args) {
@ -24,6 +25,10 @@ const backendInteractorService = credentials => ({
return followRequestFetcher.startFetching({ store, credentials })
},
startFetchingLists ({ store }) {
return listsFetcher.startFetching({ store, credentials })
},
startUserSocket ({ store }) {
const serv = store.rootState.instance.server.replace('http', 'ws')
const url = serv + getMastodonSocketURI({ credentials, stream: 'user' })

View file

@ -390,6 +390,13 @@ export const parseNotification = (data) => {
: parseUser(data.target)
output.from_profile = parseUser(data.account)
output.emoji = data.emoji
if (data.report) {
output.report = data.report
output.report.content = data.report.content
output.report.acct = parseUser(data.report.account)
output.report.actor = parseUser(data.report.actor)
output.report.statuses = data.report.statuses.map(parseStatus)
}
} else {
const parsedNotice = parseStatus(data.notice)
output.type = data.ntype

View file

@ -0,0 +1,22 @@
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
const fetchAndUpdate = ({ store, credentials }) => {
return apiService.fetchLists({ credentials })
.then(lists => {
store.commit('setLists', lists)
}, () => {})
.catch(() => {})
}
const startFetching = ({ credentials, store }) => {
const boundFetchAndUpdate = () => fetchAndUpdate({ credentials, store })
boundFetchAndUpdate()
return promiseInterval(boundFetchAndUpdate, 240000)
}
const listsFetcher = {
startFetching
}
export default listsFetcher

View file

@ -15,6 +15,7 @@ export const visibleTypes = store => {
rootState.config.notificationVisibility.followRequest && 'follow_request',
rootState.config.notificationVisibility.moves && 'move',
rootState.config.notificationVisibility.emojiReactions && 'pleroma:emoji_reaction',
rootState.config.notificationVisibility.reports && 'pleroma:report',
rootState.config.notificationVisibility.polls && 'poll'
].filter(_ => _))
}
@ -99,6 +100,9 @@ export const prepareNotificationObject = (notification, i18n) => {
case 'follow_request':
i18nString = 'follow_request'
break
case 'pleroma:report':
i18nString = 'submitted_report'
break
case 'poll':
i18nString = 'poll_ended'
break

View file

@ -1,6 +1,18 @@
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
// For using include_types when fetching notifications.
// Note: chat_mention excluded as pleroma-fe polls them separately
const mastoApiNotificationTypes = [
'mention',
'favourite',
'reblog',
'follow',
'move',
'pleroma:emoji_reaction',
'pleroma:report'
]
const update = ({ store, notifications, older }) => {
store.dispatch('addNewNotifications', { notifications, older })
}
@ -12,6 +24,7 @@ const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
const timelineData = rootState.statuses.notifications
const hideMutedPosts = getters.mergedConfig.hideMutedPosts
args.includeTypes = mastoApiNotificationTypes
args.withMuted = !hideMutedPosts
args.timeline = 'notifications'
@ -63,6 +76,7 @@ const fetchNotifications = ({ store, args, older }) => {
messageArgs: [error.message],
timeout: 5000
})
console.error(error)
})
}

View file

@ -3,12 +3,13 @@ import { camelCase } from 'lodash'
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
const update = ({ store, statuses, timeline, showImmediately, userId, pagination }) => {
const update = ({ store, statuses, timeline, showImmediately, userId, listId, pagination }) => {
const ccTimeline = camelCase(timeline)
store.dispatch('addNewStatuses', {
timeline: ccTimeline,
userId,
listId,
statuses,
showImmediately,
pagination
@ -22,6 +23,7 @@ const fetchAndUpdate = ({
older = false,
showImmediately = false,
userId = false,
listId = false,
tag = false,
until,
since
@ -44,6 +46,7 @@ const fetchAndUpdate = ({
}
args.userId = userId
args.listId = listId
args.tag = tag
args.withMuted = !hideMutedPosts
if (loggedIn && ['friends', 'public', 'publicAndExternal'].includes(timeline)) {
@ -62,7 +65,7 @@ const fetchAndUpdate = ({
if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) {
store.dispatch('queueFlush', { timeline, id: timelineData.maxId })
}
update({ store, statuses, timeline, showImmediately, userId, pagination })
update({ store, statuses, timeline, showImmediately, userId, listId, pagination })
return { statuses, pagination }
})
.catch((error) => {
@ -75,14 +78,15 @@ const fetchAndUpdate = ({
})
}
const startFetching = ({ timeline = 'friends', credentials, store, userId = false, tag = false }) => {
const startFetching = ({ timeline = 'friends', credentials, store, userId = false, listId = false, tag = false }) => {
const rootState = store.rootState || store.state
const timelineData = rootState.statuses.timelines[camelCase(timeline)]
const showImmediately = timelineData.visibleStatuses.length === 0
timelineData.userId = userId
fetchAndUpdate({ timeline, credentials, store, showImmediately, userId, tag })
timelineData.listId = listId
fetchAndUpdate({ timeline, credentials, store, showImmediately, userId, listId, tag })
const boundFetchAndUpdate = () =>
fetchAndUpdate({ timeline, credentials, store, userId, tag })
fetchAndUpdate({ timeline, credentials, store, userId, listId, tag })
return promiseInterval(boundFetchAndUpdate, 10000)
}
const timelineFetcher = {

View file

@ -40,4 +40,28 @@ describe('routes', () => {
// eslint-disable-next-line no-prototype-builtins
expect(matchedComponents[0].components.default.components.hasOwnProperty('UserCard')).to.eql(true)
})
it('list view', async () => {
await router.push('/lists')
const matchedComponents = router.currentRoute.value.matched
expect(Object.prototype.hasOwnProperty.call(matchedComponents[0].components.default.components, 'ListsCard')).to.eql(true)
})
it('list timeline', async () => {
await router.push('/lists/1')
const matchedComponents = router.currentRoute.value.matched
expect(Object.prototype.hasOwnProperty.call(matchedComponents[0].components.default.components, 'Timeline')).to.eql(true)
})
it('list edit', async () => {
await router.push('/lists/1/edit')
const matchedComponents = router.currentRoute.value.matched
expect(Object.prototype.hasOwnProperty.call(matchedComponents[0].components.default.components, 'BasicUserCard')).to.eql(true)
})
})

View file

@ -0,0 +1,83 @@
import { cloneDeep } from 'lodash'
import { defaultState, mutations, getters } from '../../../../src/modules/lists.js'
describe('The lists module', () => {
describe('mutations', () => {
it('updates array of all lists', () => {
const state = cloneDeep(defaultState)
const list = { id: '1', title: 'testList' }
mutations.setLists(state, [list])
expect(state.allLists).to.have.length(1)
expect(state.allLists).to.eql([list])
})
it('adds a new list with a title, updating the title for existing lists', () => {
const state = cloneDeep(defaultState)
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 })
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 })
expect(state.allLists).to.have.length(1)
expect(state.allLists[0]).to.eql(modList)
})
it('adds a new list with an array of IDs, updating the IDs for existing lists', () => {
const state = cloneDeep(defaultState)
const list = { id: '1', accountIds: ['1', '2', '3'] }
const modList = { id: '1', accountIds: ['3', '4', '5'] }
mutations.setListAccounts(state, list)
expect(state.allListsObject[list.id]).to.eql({ accountIds: list.accountIds })
mutations.setListAccounts(state, modList)
expect(state.allListsObject[modList.id]).to.eql({ accountIds: modList.accountIds })
})
it('deletes a list', () => {
const state = {
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
}
const id = '1'
mutations.deleteList(state, { id })
expect(state.allLists).to.have.length(0)
expect(state.allListsObject).to.eql({})
})
})
describe('getters', () => {
it('returns list title', () => {
const state = {
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
}
const id = '1'
expect(getters.findListTitle(state)(id)).to.eql('testList')
})
it('returns list accounts', () => {
const state = {
allLists: [{ id: '1', title: 'testList' }],
allListsObject: {
1: { title: 'testList', accountIds: ['1', '2', '3'] }
}
}
const id = '1'
expect(getters.findListAccounts(state)(id)).to.eql(['1', '2', '3'])
})
})
})

239
yarn.lock
View file

@ -30,7 +30,7 @@
dependencies:
"@babel/highlight" "^7.18.6"
"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.17.7":
"@babel/compat-data@^7.17.7":
version "7.17.7"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.17.7.tgz#078d8b833fbbcc95286613be8c716cef2b519fa2"
integrity sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==
@ -40,21 +40,21 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d"
integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==
"@babel/core@7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.9.tgz#805461f967c77ff46c74ca0460ccf4fe933ddd59"
integrity sha512-1LIb1eL8APMy91/IMW+31ckrfBM4yCoLaVzoDhZUKSM4cu1L1nIidyxkCgzPAgrC5WEz36IPEr/eSeSF9pIn+g==
"@babel/core@7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8"
integrity sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==
dependencies:
"@ampproject/remapping" "^2.1.0"
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.18.9"
"@babel/generator" "^7.18.10"
"@babel/helper-compilation-targets" "^7.18.9"
"@babel/helper-module-transforms" "^7.18.9"
"@babel/helpers" "^7.18.9"
"@babel/parser" "^7.18.9"
"@babel/template" "^7.18.6"
"@babel/traverse" "^7.18.9"
"@babel/types" "^7.18.9"
"@babel/parser" "^7.18.10"
"@babel/template" "^7.18.10"
"@babel/traverse" "^7.18.10"
"@babel/types" "^7.18.10"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
@ -100,6 +100,15 @@
jsesc "^2.5.1"
source-map "^0.5.0"
"@babel/generator@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.10.tgz#794f328bfabdcbaf0ebf9bf91b5b57b61fa77a2a"
integrity sha512-0+sW7e3HjQbiHbj1NeU/vN8ornohYlacAfZIaXhdoGweQqgcNy69COVciYYqEXJ/v+9OBA7Frxm4CVAuNqKeNA==
dependencies:
"@babel/types" "^7.18.10"
"@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"
@ -140,14 +149,14 @@
"@babel/helper-explode-assignable-expression" "^7.18.6"
"@babel/types" "^7.18.6"
"@babel/helper-compilation-targets@^7.13.0":
version "7.17.7"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz#a3c2924f5e5f0379b356d4cfb313d1414dc30e46"
integrity sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==
"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf"
integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==
dependencies:
"@babel/compat-data" "^7.17.7"
"@babel/helper-validator-option" "^7.16.7"
browserslist "^4.17.5"
"@babel/compat-data" "^7.18.8"
"@babel/helper-validator-option" "^7.18.6"
browserslist "^4.20.2"
semver "^6.3.0"
"@babel/helper-compilation-targets@^7.18.6":
@ -160,16 +169,6 @@
browserslist "^4.20.2"
semver "^6.3.0"
"@babel/helper-compilation-targets@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf"
integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg==
dependencies:
"@babel/compat-data" "^7.18.8"
"@babel/helper-validator-option" "^7.18.6"
browserslist "^4.20.2"
semver "^6.3.0"
"@babel/helper-create-class-features-plugin@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.6.tgz#6f15f8459f3b523b39e00a99982e2c040871ed72"
@ -199,15 +198,13 @@
"@babel/helper-annotate-as-pure" "^7.18.6"
regexpu-core "^5.1.0"
"@babel/helper-define-polyfill-provider@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665"
integrity sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==
"@babel/helper-define-polyfill-provider@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz#bd10d0aca18e8ce012755395b05a79f45eca5073"
integrity sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg==
dependencies:
"@babel/helper-compilation-targets" "^7.13.0"
"@babel/helper-module-imports" "^7.12.13"
"@babel/helper-plugin-utils" "^7.13.0"
"@babel/traverse" "^7.13.0"
"@babel/helper-compilation-targets" "^7.17.7"
"@babel/helper-plugin-utils" "^7.16.7"
debug "^4.1.1"
lodash.debounce "^4.0.8"
resolve "^1.14.2"
@ -310,13 +307,6 @@
dependencies:
"@babel/types" "^7.0.0"
"@babel/helper-module-imports@^7.12.13":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437"
integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==
dependencies:
"@babel/types" "^7.16.7"
"@babel/helper-module-imports@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e"
@ -364,7 +354,7 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250"
integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==
"@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
"@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5"
integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==
@ -389,6 +379,16 @@
"@babel/helper-wrap-function" "^7.18.6"
"@babel/types" "^7.18.6"
"@babel/helper-remap-async-to-generator@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519"
integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-wrap-function" "^7.18.9"
"@babel/types" "^7.18.9"
"@babel/helper-replace-supers@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.6.tgz#efedf51cfccea7b7b8c0f00002ab317e7abfe420"
@ -439,6 +439,11 @@
dependencies:
"@babel/types" "^7.18.6"
"@babel/helper-string-parser@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56"
integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==
"@babel/helper-validator-identifier@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad"
@ -449,11 +454,6 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
"@babel/helper-validator-option@^7.16.7":
version "7.16.7"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23"
integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==
"@babel/helper-validator-option@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
@ -469,6 +469,16 @@
"@babel/traverse" "^7.18.6"
"@babel/types" "^7.18.6"
"@babel/helper-wrap-function@^7.18.9":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.10.tgz#a7fcd3ab9b1be4c9b52cf7d7fdc1e88c2ce93396"
integrity sha512-95NLBP59VWdfK2lyLKe6eTMq9xg+yWKzxzxbJ1wcYNi1Auz200+83fMDADjRxBvc2QQor5zja2yTQzXGhk2GtQ==
dependencies:
"@babel/helper-function-name" "^7.18.9"
"@babel/template" "^7.18.10"
"@babel/traverse" "^7.18.10"
"@babel/types" "^7.18.10"
"@babel/helpers@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.6.tgz#4c966140eaa1fcaa3d5a8c09d7db61077d4debfd"
@ -523,6 +533,11 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.7.tgz#fc19b645a5456c8d6fdb6cecd3c66c0173902800"
integrity sha512-bm3AQf45vR4gKggRfvJdYJ0gFLoCbsPxiFLSH6hTVYABptNHY6l9NrhnucVjQ/X+SPtLANT9lc0fFhikj+VBRA==
"@babel/parser@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.10.tgz#94b5f8522356e69e8277276adf67ed280c90ecc1"
integrity sha512-TYk3OA0HKL6qNryUayb5UUEhM/rkOQozIBEA5ITXh5DWrSp0TlUQXMyZmnWxG/DizSWBeeQ0Zbc5z8UGaaqoeg==
"@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"
@ -549,14 +564,14 @@
"@babel/helper-skip-transparent-expression-wrappers" "^7.18.9"
"@babel/plugin-proposal-optional-chaining" "^7.18.9"
"@babel/plugin-proposal-async-generator-functions@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.6.tgz#aedac81e6fc12bb643374656dd5f2605bf743d17"
integrity sha512-WAz4R9bvozx4qwf74M+sfqPMKfSqwM0phxPTR6iJIi8robgzXwkEgmeJG1gEKhm6sDqT/U9aV3lfcqybIpev8w==
"@babel/plugin-proposal-async-generator-functions@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.10.tgz#85ea478c98b0095c3e4102bff3b67d306ed24952"
integrity sha512-1mFuY2TOsR1hxbjCo4QL+qlIjV07p4H4EUYw2J/WCqsvFV6V9X9z9YhXbWndc/4fw+hYGlDT7egYxliMp5O6Ew==
dependencies:
"@babel/helper-environment-visitor" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/helper-remap-async-to-generator" "^7.18.6"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-plugin-utils" "^7.18.9"
"@babel/helper-remap-async-to-generator" "^7.18.9"
"@babel/plugin-syntax-async-generators" "^7.8.4"
"@babel/plugin-proposal-class-properties@^7.18.6":
@ -1007,16 +1022,16 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-transform-runtime@7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.9.tgz#d9e4b1b25719307bfafbf43065ed7fb3a83adb8f"
integrity sha512-wS8uJwBt7/b/mzE13ktsJdmS4JP/j7PQSaADtnb4I2wL0zK51MQ0pmF8/Jy0wUIS96fr+fXT6S/ifiPXnvrlSg==
"@babel/plugin-transform-runtime@7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.18.10.tgz#37d14d1fa810a368fd635d4d1476c0154144a96f"
integrity sha512-q5mMeYAdfEbpBAgzl7tBre/la3LeCxmDO1+wMXRdPWbcoMjR3GiXlCLk7JBZVVye0bqTGNMbt0yYVXX1B1jEWQ==
dependencies:
"@babel/helper-module-imports" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.9"
babel-plugin-polyfill-corejs2 "^0.3.1"
babel-plugin-polyfill-corejs3 "^0.5.2"
babel-plugin-polyfill-regenerator "^0.3.1"
babel-plugin-polyfill-corejs2 "^0.3.2"
babel-plugin-polyfill-corejs3 "^0.5.3"
babel-plugin-polyfill-regenerator "^0.4.0"
semver "^6.3.0"
"@babel/plugin-transform-shorthand-properties@^7.18.6":
@ -1055,12 +1070,12 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.9"
"@babel/plugin-transform-unicode-escapes@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.6.tgz#0d01fb7fb2243ae1c033f65f6e3b4be78db75f27"
integrity sha512-XNRwQUXYMP7VLuy54cr/KS/WeL3AZeORhrmeZ7iewgu+X2eBqmpaLI/hzqr9ZxCeUoq0ASK4GUzSM0BDhZkLFw==
"@babel/plugin-transform-unicode-escapes@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246"
integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.9"
"@babel/plugin-transform-unicode-regex@^7.18.6":
version "7.18.6"
@ -1070,10 +1085,10 @@
"@babel/helper-create-regexp-features-plugin" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/preset-env@7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.9.tgz#9b3425140d724fbe590322017466580844c7eaff"
integrity sha512-75pt/q95cMIHWssYtyfjVlvI+QEZQThQbKvR9xH+F/Agtw/s4Wfc2V9Bwd/P39VtixB7oWxGdH4GteTTwYJWMg==
"@babel/preset-env@7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.10.tgz#83b8dfe70d7eea1aae5a10635ab0a5fe60dfc0f4"
integrity sha512-wVxs1yjFdW3Z/XkNfXKoblxoHgbtUF7/l3PvvP4m02Qz9TZ6uZGxRVYjSQeR87oQmHco9zWitW5J82DJ7sCjvA==
dependencies:
"@babel/compat-data" "^7.18.8"
"@babel/helper-compilation-targets" "^7.18.9"
@ -1081,7 +1096,7 @@
"@babel/helper-validator-option" "^7.18.6"
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6"
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9"
"@babel/plugin-proposal-async-generator-functions" "^7.18.6"
"@babel/plugin-proposal-async-generator-functions" "^7.18.10"
"@babel/plugin-proposal-class-properties" "^7.18.6"
"@babel/plugin-proposal-class-static-block" "^7.18.6"
"@babel/plugin-proposal-dynamic-import" "^7.18.6"
@ -1141,13 +1156,13 @@
"@babel/plugin-transform-sticky-regex" "^7.18.6"
"@babel/plugin-transform-template-literals" "^7.18.9"
"@babel/plugin-transform-typeof-symbol" "^7.18.9"
"@babel/plugin-transform-unicode-escapes" "^7.18.6"
"@babel/plugin-transform-unicode-escapes" "^7.18.10"
"@babel/plugin-transform-unicode-regex" "^7.18.6"
"@babel/preset-modules" "^0.1.5"
"@babel/types" "^7.18.9"
babel-plugin-polyfill-corejs2 "^0.3.1"
babel-plugin-polyfill-corejs3 "^0.5.2"
babel-plugin-polyfill-regenerator "^0.3.1"
"@babel/types" "^7.18.10"
babel-plugin-polyfill-corejs2 "^0.3.2"
babel-plugin-polyfill-corejs3 "^0.5.3"
babel-plugin-polyfill-regenerator "^0.4.0"
core-js-compat "^3.22.1"
semver "^6.3.0"
@ -1196,6 +1211,15 @@
"@babel/parser" "^7.16.7"
"@babel/types" "^7.16.7"
"@babel/template@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71"
integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/parser" "^7.18.10"
"@babel/types" "^7.18.10"
"@babel/template@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31"
@ -1205,7 +1229,7 @@
"@babel/parser" "^7.18.6"
"@babel/types" "^7.18.6"
"@babel/traverse@^7.0.0", "@babel/traverse@^7.13.0":
"@babel/traverse@^7.0.0":
version "7.17.3"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.17.3.tgz#0ae0f15b27d9a92ba1f2263358ea7c4e7db47b57"
integrity sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==
@ -1221,6 +1245,22 @@
debug "^4.1.0"
globals "^11.1.0"
"@babel/traverse@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.10.tgz#37ad97d1cb00efa869b91dd5d1950f8a6cf0cb08"
integrity sha512-J7ycxg0/K9XCtLyHf0cz2DqDihonJeIo+z+HEdRe9YuT8TY4A66i+Ab2/xZCEW7Ro60bPCBBfqqboHSamoV3+g==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.18.10"
"@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.10"
"@babel/types" "^7.18.10"
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"
@ -1269,6 +1309,15 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
"@babel/types@^7.18.10":
version "7.18.10"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.10.tgz#4908e81b6b339ca7c6b7a555a5fc29446f26dde6"
integrity sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==
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"
@ -2408,29 +2457,29 @@ babel-plugin-lodash@3.3.4:
lodash "^4.17.10"
require-package-name "^2.0.1"
babel-plugin-polyfill-corejs2@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz#440f1b70ccfaabc6b676d196239b138f8a2cfba5"
integrity sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==
babel-plugin-polyfill-corejs2@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz#e4c31d4c89b56f3cf85b92558954c66b54bd972d"
integrity sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q==
dependencies:
"@babel/compat-data" "^7.13.11"
"@babel/helper-define-polyfill-provider" "^0.3.1"
"@babel/compat-data" "^7.17.7"
"@babel/helper-define-polyfill-provider" "^0.3.2"
semver "^6.1.1"
babel-plugin-polyfill-corejs3@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72"
integrity sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ==
babel-plugin-polyfill-corejs3@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7"
integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==
dependencies:
"@babel/helper-define-polyfill-provider" "^0.3.1"
"@babel/helper-define-polyfill-provider" "^0.3.2"
core-js-compat "^3.21.0"
babel-plugin-polyfill-regenerator@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990"
integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==
babel-plugin-polyfill-regenerator@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz#8f51809b6d5883e07e71548d75966ff7635527fe"
integrity sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw==
dependencies:
"@babel/helper-define-polyfill-provider" "^0.3.1"
"@babel/helper-define-polyfill-provider" "^0.3.2"
babel-register@^6.26.0:
version "6.26.0"
@ -2704,7 +2753,7 @@ browserslist@^4.12.0:
escalade "^3.0.1"
node-releases "^1.1.58"
browserslist@^4.17.5, browserslist@^4.19.1:
browserslist@^4.19.1:
version "4.20.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88"
integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==