Merge branch 'issue-436-mastoapi-notifications' into 'develop'
#436: integrate mastoAPI notifications Closes #436 See merge request pleroma/pleroma-fe!678
This commit is contained in:
commit
84505e01f5
15 changed files with 205 additions and 6790 deletions
|
@ -28,21 +28,21 @@ const Notification = {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
userClass () {
|
userClass () {
|
||||||
return highlightClass(this.notification.action.user)
|
return highlightClass(this.notification.from_profile)
|
||||||
},
|
},
|
||||||
userStyle () {
|
userStyle () {
|
||||||
const highlight = this.$store.state.config.highlight
|
const highlight = this.$store.state.config.highlight
|
||||||
const user = this.notification.action.user
|
const user = this.notification.from_profile
|
||||||
return highlightStyle(highlight[user.screen_name])
|
return highlightStyle(highlight[user.screen_name])
|
||||||
},
|
},
|
||||||
userInStore () {
|
userInStore () {
|
||||||
return this.$store.getters.findUser(this.notification.action.user.id)
|
return this.$store.getters.findUser(this.notification.from_profile.id)
|
||||||
},
|
},
|
||||||
user () {
|
user () {
|
||||||
if (this.userInStore) {
|
if (this.userInStore) {
|
||||||
return this.userInStore
|
return this.userInStore
|
||||||
}
|
}
|
||||||
return {}
|
return this.notification.from_profile
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<status v-if="notification.type === 'mention'" :compact="true" :statusoid="notification.status"></status>
|
<status
|
||||||
|
v-if="notification.type === 'mention'"
|
||||||
|
:compact="true"
|
||||||
|
:statusoid="notification.status"
|
||||||
|
>
|
||||||
|
</status>
|
||||||
<div class="non-mention" :class="[userClass, { highlighted: userStyle }]" :style="[ userStyle ]" v-else>
|
<div class="non-mention" :class="[userClass, { highlighted: userStyle }]" :style="[ userStyle ]" v-else>
|
||||||
<a class='avatar-container' :href="notification.action.user.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded">
|
<a class='avatar-container' :href="notification.from_profile.statusnet_profile_url" @click.stop.prevent.capture="toggleUserExpanded">
|
||||||
<UserAvatar :compact="true" :betterShadow="betterShadow" :src="notification.action.user.profile_image_url_original"/>
|
<UserAvatar :compact="true" :betterShadow="betterShadow" :src="notification.from_profile.profile_image_url_original" />
|
||||||
</a>
|
</a>
|
||||||
<div class='notification-right'>
|
<div class='notification-right'>
|
||||||
<UserCard :user="getUser(notification)" :rounded="true" :bordered="true" v-if="userExpanded"/>
|
<UserCard :user="getUser(notification)" :rounded="true" :bordered="true" v-if="userExpanded" />
|
||||||
<span class="notification-details">
|
<span class="notification-details">
|
||||||
<div class="name-and-action">
|
<div class="name-and-action">
|
||||||
<span class="username" v-if="!!notification.action.user.name_html" :title="'@'+notification.action.user.screen_name" v-html="notification.action.user.name_html"></span>
|
<span class="username" v-if="!!notification.from_profile.name_html" :title="'@'+notification.from_profile.screen_name" v-html="notification.from_profile.name_html"></span>
|
||||||
<span class="username" v-else :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
|
<span class="username" v-else :title="'@'+notification.from_profile.screen_name">{{ notification.from_profile.name }}</span>
|
||||||
<span v-if="notification.type === 'like'">
|
<span v-if="notification.type === 'like'">
|
||||||
<i class="fa icon-star lit"></i>
|
<i class="fa icon-star lit"></i>
|
||||||
<small>{{$t('notifications.favorited_you')}}</small>
|
<small>{{$t('notifications.favorited_you')}}</small>
|
||||||
|
@ -23,19 +28,24 @@
|
||||||
<small>{{$t('notifications.followed_you')}}</small>
|
<small>{{$t('notifications.followed_you')}}</small>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="timeago">
|
<div class="timeago" v-if="notification.type === 'follow'">
|
||||||
|
<span class="faint">
|
||||||
|
<timeago :since="notification.created_at" :auto-update="240"></timeago>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="timeago" v-else>
|
||||||
<router-link v-if="notification.status" :to="{ name: 'conversation', params: { id: notification.status.id } }" class="faint-link">
|
<router-link v-if="notification.status" :to="{ name: 'conversation', params: { id: notification.status.id } }" class="faint-link">
|
||||||
<timeago :since="notification.action.created_at" :auto-update="240"></timeago>
|
<timeago :since="notification.created_at" :auto-update="240"></timeago>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<div class="follow-text" v-if="notification.type === 'follow'">
|
<div class="follow-text" v-if="notification.type === 'follow'">
|
||||||
<router-link :to="userProfileLink(notification.action.user)">
|
<router-link :to="userProfileLink(notification.from_profile)">
|
||||||
@{{notification.action.user.screen_name}}
|
@{{notification.from_profile.screen_name}}
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<status class="faint" :compact="true" :statusoid="notification.status" :noHeading="true"></status>
|
<status class="faint" :compact="true" :statusoid="notification.action" :noHeading="true"></status>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -49,7 +49,7 @@ const Notifications = {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
markAsSeen () {
|
markAsSeen () {
|
||||||
this.$store.dispatch('markNotificationsAsSeen', this.visibleNotifications)
|
this.$store.dispatch('markNotificationsAsSeen')
|
||||||
},
|
},
|
||||||
fetchOlderNotifications () {
|
fetchOlderNotifications () {
|
||||||
const store = this.$store
|
const store = this.$store
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<button v-if="unseenCount" @click.prevent="markAsSeen" class="read-button">{{$t('notifications.read')}}</button>
|
<button v-if="unseenCount" @click.prevent="markAsSeen" class="read-button">{{$t('notifications.read')}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div v-for="notification in visibleNotifications" :key="notification.action.id" class="notification" :class='{"unseen": !notification.seen}'>
|
<div v-for="notification in visibleNotifications" :key="notification.id" class="notification" :class='{"unseen": !notification.seen}'>
|
||||||
<div class="notification-overlay"></div>
|
<div class="notification-overlay"></div>
|
||||||
<notification :notification="notification"></notification>
|
<notification :notification="notification"></notification>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -59,6 +59,11 @@ const persistedStateOptions = {
|
||||||
const persistedState = await createPersistedState(persistedStateOptions)
|
const persistedState = await createPersistedState(persistedStateOptions)
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
modules: {
|
modules: {
|
||||||
|
i18n: {
|
||||||
|
getters: {
|
||||||
|
i18n: () => i18n
|
||||||
|
}
|
||||||
|
},
|
||||||
interface: interfaceModule,
|
interface: interfaceModule,
|
||||||
instance: instanceModule,
|
instance: instanceModule,
|
||||||
statuses: statusesModule,
|
statuses: statusesModule,
|
||||||
|
|
|
@ -272,12 +272,14 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes }) => {
|
const addNewNotifications = (state, { dispatch, notifications, older, visibleNotificationTypes, rootGetters }) => {
|
||||||
const allStatuses = state.allStatuses
|
const allStatuses = state.allStatuses
|
||||||
const allStatusesObject = state.allStatusesObject
|
const allStatusesObject = state.allStatusesObject
|
||||||
each(notifications, (notification) => {
|
each(notifications, (notification) => {
|
||||||
|
if (notification.type !== 'follow') {
|
||||||
notification.action = mergeOrAdd(allStatuses, allStatusesObject, notification.action).item
|
notification.action = mergeOrAdd(allStatuses, allStatusesObject, notification.action).item
|
||||||
notification.status = notification.status && mergeOrAdd(allStatuses, allStatusesObject, notification.status).item
|
notification.status = notification.status && mergeOrAdd(allStatuses, allStatusesObject, notification.status).item
|
||||||
|
}
|
||||||
|
|
||||||
// Only add a new notification if we don't have one for the same action
|
// Only add a new notification if we don't have one for the same action
|
||||||
if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
|
if (!state.notifications.idStore.hasOwnProperty(notification.id)) {
|
||||||
|
@ -293,15 +295,32 @@ const addNewNotifications = (state, { dispatch, notifications, older, visibleNot
|
||||||
|
|
||||||
if ('Notification' in window && window.Notification.permission === 'granted') {
|
if ('Notification' in window && window.Notification.permission === 'granted') {
|
||||||
const notifObj = {}
|
const notifObj = {}
|
||||||
const action = notification.action
|
const status = notification.status
|
||||||
const title = action.user.name
|
const title = notification.from_profile.name
|
||||||
notifObj.icon = action.user.profile_image_url
|
notifObj.icon = notification.from_profile.profile_image_url
|
||||||
notifObj.body = action.text // there's a problem that it doesn't put a space before links tho
|
let i18nString
|
||||||
|
switch (notification.type) {
|
||||||
|
case 'like':
|
||||||
|
i18nString = 'favorited_you'
|
||||||
|
break
|
||||||
|
case 'repeat':
|
||||||
|
i18nString = 'repeated_you'
|
||||||
|
break
|
||||||
|
case 'follow':
|
||||||
|
i18nString = 'followed_you'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i18nString) {
|
||||||
|
notifObj.body = rootGetters.i18n.t('notifications.' + i18nString)
|
||||||
|
} else {
|
||||||
|
notifObj.body = notification.status.text
|
||||||
|
}
|
||||||
|
|
||||||
// Shows first attached non-nsfw image, if any. Should add configuration for this somehow...
|
// Shows first attached non-nsfw image, if any. Should add configuration for this somehow...
|
||||||
if (action.attachments && action.attachments.length > 0 && !action.nsfw &&
|
if (status && status.attachments && status.attachments.length > 0 && !status.nsfw &&
|
||||||
action.attachments[0].mimetype.startsWith('image/')) {
|
status.attachments[0].mimetype.startsWith('image/')) {
|
||||||
notifObj.image = action.attachments[0].url
|
notifObj.image = status.attachments[0].url
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) {
|
if (!notification.seen && !state.notifications.desktopNotificationSilence && visibleNotificationTypes.includes(notification.type)) {
|
||||||
|
@ -421,8 +440,8 @@ const statuses = {
|
||||||
addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId }) {
|
addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false, userId }) {
|
||||||
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId })
|
commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser, userId })
|
||||||
},
|
},
|
||||||
addNewNotifications ({ rootState, commit, dispatch }, { notifications, older }) {
|
addNewNotifications ({ rootState, commit, dispatch, rootGetters }, { notifications, older }) {
|
||||||
commit('addNewNotifications', { visibleNotificationTypes: visibleNotificationTypes(rootState), dispatch, notifications, older })
|
commit('addNewNotifications', { visibleNotificationTypes: visibleNotificationTypes(rootState), dispatch, notifications, older, rootGetters })
|
||||||
},
|
},
|
||||||
setError ({ rootState, commit }, { value }) {
|
setError ({ rootState, commit }, { value }) {
|
||||||
commit('setError', { value })
|
commit('setError', { value })
|
||||||
|
|
|
@ -144,8 +144,10 @@ export const mutations = {
|
||||||
status.user = state.usersObject[status.user.id]
|
status.user = state.usersObject[status.user.id]
|
||||||
},
|
},
|
||||||
setUserForNotification (state, notification) {
|
setUserForNotification (state, notification) {
|
||||||
|
if (notification.type !== 'follow') {
|
||||||
notification.action.user = state.usersObject[notification.action.user.id]
|
notification.action.user = state.usersObject[notification.action.user.id]
|
||||||
notification.from_profile = state.usersObject[notification.action.user.id]
|
}
|
||||||
|
notification.from_profile = state.usersObject[notification.from_profile.id]
|
||||||
},
|
},
|
||||||
setColor (state, { user: { id }, highlighted }) {
|
setColor (state, { user: { id }, highlighted }) {
|
||||||
const user = state.usersObject[id]
|
const user = state.usersObject[id]
|
||||||
|
|
|
@ -8,7 +8,6 @@ const BG_UPDATE_URL = '/api/qvitter/update_background_image.json'
|
||||||
const BANNER_UPDATE_URL = '/api/account/update_profile_banner.json'
|
const BANNER_UPDATE_URL = '/api/account/update_profile_banner.json'
|
||||||
const PROFILE_UPDATE_URL = '/api/account/update_profile.json'
|
const PROFILE_UPDATE_URL = '/api/account/update_profile.json'
|
||||||
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
|
const EXTERNAL_PROFILE_URL = '/api/externalprofile/show.json'
|
||||||
const QVITTER_USER_NOTIFICATIONS_URL = '/api/qvitter/statuses/notifications.json'
|
|
||||||
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
|
const QVITTER_USER_NOTIFICATIONS_READ_URL = '/api/qvitter/statuses/notifications/read.json'
|
||||||
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
|
const FOLLOW_IMPORT_URL = '/api/pleroma/follow_import'
|
||||||
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
|
const DELETE_ACCOUNT_URL = '/api/pleroma/delete_account'
|
||||||
|
@ -23,6 +22,7 @@ const ADMIN_USER_URL = '/api/pleroma/admin/user'
|
||||||
const SUGGESTIONS_URL = '/api/v1/suggestions'
|
const SUGGESTIONS_URL = '/api/v1/suggestions'
|
||||||
|
|
||||||
const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
|
const MASTODON_USER_FAVORITES_TIMELINE_URL = '/api/v1/favourites'
|
||||||
|
const MASTODON_USER_NOTIFICATIONS_URL = '/api/v1/notifications'
|
||||||
const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite`
|
const MASTODON_FAVORITE_URL = id => `/api/v1/statuses/${id}/favourite`
|
||||||
const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite`
|
const MASTODON_UNFAVORITE_URL = id => `/api/v1/statuses/${id}/unfavourite`
|
||||||
const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog`
|
const MASTODON_RETWEET_URL = id => `/api/v1/statuses/${id}/reblog`
|
||||||
|
@ -442,7 +442,7 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
|
||||||
friends: MASTODON_USER_HOME_TIMELINE_URL,
|
friends: MASTODON_USER_HOME_TIMELINE_URL,
|
||||||
mentions: MENTIONS_URL,
|
mentions: MENTIONS_URL,
|
||||||
dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
|
dms: MASTODON_DIRECT_MESSAGES_TIMELINE_URL,
|
||||||
notifications: QVITTER_USER_NOTIFICATIONS_URL,
|
notifications: MASTODON_USER_NOTIFICATIONS_URL,
|
||||||
'publicAndExternal': MASTODON_PUBLIC_TIMELINE,
|
'publicAndExternal': MASTODON_PUBLIC_TIMELINE,
|
||||||
user: MASTODON_USER_TIMELINE_URL,
|
user: MASTODON_USER_TIMELINE_URL,
|
||||||
media: MASTODON_USER_TIMELINE_URL,
|
media: MASTODON_USER_TIMELINE_URL,
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const parseUser = (data) => {
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
// output.name = ??? missing
|
output.name = data.display_name
|
||||||
output.name_html = addEmojis(data.display_name, data.emojis)
|
output.name_html = addEmojis(data.display_name, data.emojis)
|
||||||
|
|
||||||
// output.description = ??? missing
|
// output.description = ??? missing
|
||||||
|
@ -74,7 +74,7 @@ export const parseUser = (data) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Missing, trying to recover
|
// TODO: handle is_local
|
||||||
output.is_local = !output.screen_name.includes('@')
|
output.is_local = !output.screen_name.includes('@')
|
||||||
} else {
|
} else {
|
||||||
output.screen_name = data.screen_name
|
output.screen_name = data.screen_name
|
||||||
|
@ -239,7 +239,6 @@ export const parseStatus = (data) => {
|
||||||
output.in_reply_to_status_id = data.in_reply_to_status_id
|
output.in_reply_to_status_id = data.in_reply_to_status_id
|
||||||
output.in_reply_to_user_id = data.in_reply_to_user_id
|
output.in_reply_to_user_id = data.in_reply_to_user_id
|
||||||
output.in_reply_to_screen_name = data.in_reply_to_screen_name
|
output.in_reply_to_screen_name = data.in_reply_to_screen_name
|
||||||
|
|
||||||
output.statusnet_conversation_id = data.statusnet_conversation_id
|
output.statusnet_conversation_id = data.statusnet_conversation_id
|
||||||
|
|
||||||
if (output.type === 'retweet') {
|
if (output.type === 'retweet') {
|
||||||
|
@ -290,9 +289,11 @@ export const parseNotification = (data) => {
|
||||||
|
|
||||||
if (masto) {
|
if (masto) {
|
||||||
output.type = mastoDict[data.type] || data.type
|
output.type = mastoDict[data.type] || data.type
|
||||||
// output.seen = ??? missing
|
output.seen = data.pleroma.is_seen
|
||||||
output.status = parseStatus(data.status)
|
output.status = output.type === 'follow'
|
||||||
output.action = output.status // not sure
|
? null
|
||||||
|
: parseStatus(data.status)
|
||||||
|
output.action = output.status // TODO: Refactor, this is unneeded
|
||||||
output.from_profile = parseUser(data.account)
|
output.from_profile = parseUser(data.account)
|
||||||
} else {
|
} else {
|
||||||
const parsedNotice = parseStatus(data.notice)
|
const parsedNotice = parseStatus(data.notice)
|
||||||
|
|
|
@ -10,8 +10,8 @@ export const visibleTypes = store => ([
|
||||||
].filter(_ => _))
|
].filter(_ => _))
|
||||||
|
|
||||||
const sortById = (a, b) => {
|
const sortById = (a, b) => {
|
||||||
const seqA = Number(a.action.id)
|
const seqA = Number(a.id)
|
||||||
const seqB = Number(b.action.id)
|
const seqB = Number(b.id)
|
||||||
const isSeqA = !Number.isNaN(seqA)
|
const isSeqA = !Number.isNaN(seqA)
|
||||||
const isSeqB = !Number.isNaN(seqB)
|
const isSeqB = !Number.isNaN(seqB)
|
||||||
if (isSeqA && isSeqB) {
|
if (isSeqA && isSeqB) {
|
||||||
|
@ -21,7 +21,7 @@ const sortById = (a, b) => {
|
||||||
} else if (!isSeqA && isSeqB) {
|
} else if (!isSeqA && isSeqB) {
|
||||||
return -1
|
return -1
|
||||||
} else {
|
} else {
|
||||||
return a.action.id > b.action.id ? -1 : 1
|
return a.id > b.id ? -1 : 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,29 +11,35 @@ const fetchAndUpdate = ({store, credentials, older = false}) => {
|
||||||
const rootState = store.rootState || store.state
|
const rootState = store.rootState || store.state
|
||||||
const timelineData = rootState.statuses.notifications
|
const timelineData = rootState.statuses.notifications
|
||||||
|
|
||||||
|
args['timeline'] = 'notifications'
|
||||||
if (older) {
|
if (older) {
|
||||||
if (timelineData.minId !== Number.POSITIVE_INFINITY) {
|
if (timelineData.minId !== Number.POSITIVE_INFINITY) {
|
||||||
args['until'] = timelineData.minId
|
args['until'] = timelineData.minId
|
||||||
}
|
}
|
||||||
|
return fetchNotifications({ store, args, older })
|
||||||
} else {
|
} else {
|
||||||
// load unread notifications repeadedly to provide consistency between browser tabs
|
// fetch new notifications
|
||||||
|
if (timelineData.maxId !== Number.POSITIVE_INFINITY) {
|
||||||
|
args['since'] = timelineData.maxId
|
||||||
|
}
|
||||||
|
const result = fetchNotifications({ store, args, older })
|
||||||
|
|
||||||
|
// load unread notifications repeatedly to provide consistency between browser tabs
|
||||||
const notifications = timelineData.data
|
const notifications = timelineData.data
|
||||||
const unread = notifications.filter(n => !n.seen).map(n => n.id)
|
const unread = notifications.filter(n => !n.seen).map(n => n.id)
|
||||||
if (!unread.length) {
|
if (unread.length) {
|
||||||
args['since'] = timelineData.maxId
|
args['since'] = Math.min(...unread)
|
||||||
} else {
|
fetchNotifications({ store, args, older })
|
||||||
args['since'] = Math.min(...unread) - 1
|
|
||||||
if (timelineData.maxId !== Math.max(...unread)) {
|
|
||||||
args['until'] = Math.max(...unread, args['since'] + 20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args['timeline'] = 'notifications'
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchNotifications = ({ store, args, older }) => {
|
||||||
return apiService.fetchTimeline(args)
|
return apiService.fetchTimeline(args)
|
||||||
.then((notifications) => {
|
.then((notifications) => {
|
||||||
update({store, notifications, older})
|
update({ store, notifications, older })
|
||||||
return notifications
|
return notifications
|
||||||
}, () => store.dispatch('setNotificationsError', { value: true }))
|
}, () => store.dispatch('setNotificationsError', { value: true }))
|
||||||
.catch(() => store.dispatch('setNotificationsError', { value: true }))
|
.catch(() => store.dispatch('setNotificationsError', { value: true }))
|
||||||
|
|
125
test/fixtures/mastoapi.json
vendored
125
test/fixtures/mastoapi.json
vendored
|
@ -58,7 +58,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639",
|
"uri": "https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639",
|
||||||
"url": "https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639",
|
"url": "https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -127,7 +130,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/6634d32b-96a8-4852-a3db-ac8730715779",
|
"uri": "https://shigusegubu.club/objects/6634d32b-96a8-4852-a3db-ac8730715779",
|
||||||
"url": "https://shigusegubu.club/objects/6634d32b-96a8-4852-a3db-ac8730715779",
|
"url": "https://shigusegubu.club/objects/6634d32b-96a8-4852-a3db-ac8730715779",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -250,7 +256,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://pleroma.soykaf.com/objects/bf7e43d4-5048-4176-8519-58e3e1014f8b",
|
"uri": "https://pleroma.soykaf.com/objects/bf7e43d4-5048-4176-8519-58e3e1014f8b",
|
||||||
"url": "https://pleroma.soykaf.com/objects/bf7e43d4-5048-4176-8519-58e3e1014f8b",
|
"url": "https://pleroma.soykaf.com/objects/bf7e43d4-5048-4176-8519-58e3e1014f8b",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"reblogged": false,
|
"reblogged": false,
|
||||||
"reblogs_count": 0,
|
"reblogs_count": 0,
|
||||||
|
@ -260,7 +269,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://pleroma.soykaf.com/objects/bf7e43d4-5048-4176-8519-58e3e1014f8b",
|
"uri": "https://pleroma.soykaf.com/objects/bf7e43d4-5048-4176-8519-58e3e1014f8b",
|
||||||
"url": "https://pleroma.soykaf.com/objects/bf7e43d4-5048-4176-8519-58e3e1014f8b",
|
"url": "https://pleroma.soykaf.com/objects/bf7e43d4-5048-4176-8519-58e3e1014f8b",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -329,7 +341,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/0f963ca1-a263-41ca-a43c-b5d26d0a08e9",
|
"uri": "https://shigusegubu.club/objects/0f963ca1-a263-41ca-a43c-b5d26d0a08e9",
|
||||||
"url": "https://shigusegubu.club/objects/0f963ca1-a263-41ca-a43c-b5d26d0a08e9",
|
"url": "https://shigusegubu.club/objects/0f963ca1-a263-41ca-a43c-b5d26d0a08e9",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -390,7 +405,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/3f809bd8-656f-4a29-81d4-80eed6916eb0",
|
"uri": "https://shigusegubu.club/objects/3f809bd8-656f-4a29-81d4-80eed6916eb0",
|
||||||
"url": "https://shigusegubu.club/objects/3f809bd8-656f-4a29-81d4-80eed6916eb0",
|
"url": "https://shigusegubu.club/objects/3f809bd8-656f-4a29-81d4-80eed6916eb0",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -516,7 +534,10 @@
|
||||||
}],
|
}],
|
||||||
"uri": "tag:social.super-niche.club,2019-01-17:noticeId=2353002:objectType=note",
|
"uri": "tag:social.super-niche.club,2019-01-17:noticeId=2353002:objectType=note",
|
||||||
"url": "https://social.super-niche.club/notice/2353002",
|
"url": "https://social.super-niche.club/notice/2353002",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"reblogged": false,
|
"reblogged": false,
|
||||||
"reblogs_count": 0,
|
"reblogs_count": 0,
|
||||||
|
@ -529,7 +550,10 @@
|
||||||
}],
|
}],
|
||||||
"uri": "tag:social.super-niche.club,2019-01-17:noticeId=2353002:objectType=note",
|
"uri": "tag:social.super-niche.club,2019-01-17:noticeId=2353002:objectType=note",
|
||||||
"url": "tag:social.super-niche.club,2019-01-17:noticeId=2353002:objectType=note",
|
"url": "tag:social.super-niche.club,2019-01-17:noticeId=2353002:objectType=note",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -657,7 +681,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://miniwa.moe/objects/448e2944-0ecd-457f-92c3-cb454f2b0fab",
|
"uri": "https://miniwa.moe/objects/448e2944-0ecd-457f-92c3-cb454f2b0fab",
|
||||||
"url": "https://miniwa.moe/objects/448e2944-0ecd-457f-92c3-cb454f2b0fab",
|
"url": "https://miniwa.moe/objects/448e2944-0ecd-457f-92c3-cb454f2b0fab",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"reblogged": false,
|
"reblogged": false,
|
||||||
"reblogs_count": 0,
|
"reblogs_count": 0,
|
||||||
|
@ -667,7 +694,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://miniwa.moe/objects/448e2944-0ecd-457f-92c3-cb454f2b0fab",
|
"uri": "https://miniwa.moe/objects/448e2944-0ecd-457f-92c3-cb454f2b0fab",
|
||||||
"url": "https://miniwa.moe/objects/448e2944-0ecd-457f-92c3-cb454f2b0fab",
|
"url": "https://miniwa.moe/objects/448e2944-0ecd-457f-92c3-cb454f2b0fab",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -733,7 +763,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/38b1bc44-15d8-40dd-b1aa-937e0ff4a86d",
|
"uri": "https://shigusegubu.club/objects/38b1bc44-15d8-40dd-b1aa-937e0ff4a86d",
|
||||||
"url": "https://shigusegubu.club/objects/38b1bc44-15d8-40dd-b1aa-937e0ff4a86d",
|
"url": "https://shigusegubu.club/objects/38b1bc44-15d8-40dd-b1aa-937e0ff4a86d",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -794,7 +827,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/fbff5da4-a517-42a9-bca9-17cae8cf2542",
|
"uri": "https://shigusegubu.club/objects/fbff5da4-a517-42a9-bca9-17cae8cf2542",
|
||||||
"url": "https://shigusegubu.club/objects/fbff5da4-a517-42a9-bca9-17cae8cf2542",
|
"url": "https://shigusegubu.club/objects/fbff5da4-a517-42a9-bca9-17cae8cf2542",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -850,7 +886,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/4007d659-27c6-4577-be10-fd134f5e4e7e",
|
"uri": "https://shigusegubu.club/objects/4007d659-27c6-4577-be10-fd134f5e4e7e",
|
||||||
"url": "https://shigusegubu.club/objects/4007d659-27c6-4577-be10-fd134f5e4e7e",
|
"url": "https://shigusegubu.club/objects/4007d659-27c6-4577-be10-fd134f5e4e7e",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -906,7 +945,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/59912d51-1cc6-4dc7-828c-f167e6c8b391",
|
"uri": "https://shigusegubu.club/objects/59912d51-1cc6-4dc7-828c-f167e6c8b391",
|
||||||
"url": "https://shigusegubu.club/objects/59912d51-1cc6-4dc7-828c-f167e6c8b391",
|
"url": "https://shigusegubu.club/objects/59912d51-1cc6-4dc7-828c-f167e6c8b391",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -962,7 +1004,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/62690bce-3f49-4047-9c8e-8941f2f79e10",
|
"uri": "https://shigusegubu.club/objects/62690bce-3f49-4047-9c8e-8941f2f79e10",
|
||||||
"url": "https://shigusegubu.club/objects/62690bce-3f49-4047-9c8e-8941f2f79e10",
|
"url": "https://shigusegubu.club/objects/62690bce-3f49-4047-9c8e-8941f2f79e10",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -1023,7 +1068,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/818f3dd0-2ff8-4def-a170-e4d4c405f387",
|
"uri": "https://shigusegubu.club/objects/818f3dd0-2ff8-4def-a170-e4d4c405f387",
|
||||||
"url": "https://shigusegubu.club/objects/818f3dd0-2ff8-4def-a170-e4d4c405f387",
|
"url": "https://shigusegubu.club/objects/818f3dd0-2ff8-4def-a170-e4d4c405f387",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -1084,7 +1132,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/0783a193-c097-488d-8944-47df9372cd6e",
|
"uri": "https://shigusegubu.club/objects/0783a193-c097-488d-8944-47df9372cd6e",
|
||||||
"url": "https://shigusegubu.club/objects/0783a193-c097-488d-8944-47df9372cd6e",
|
"url": "https://shigusegubu.club/objects/0783a193-c097-488d-8944-47df9372cd6e",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -1145,7 +1196,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/145d5252-7b8e-467d-9f36-1db0818f452f",
|
"uri": "https://shigusegubu.club/objects/145d5252-7b8e-467d-9f36-1db0818f452f",
|
||||||
"url": "https://shigusegubu.club/objects/145d5252-7b8e-467d-9f36-1db0818f452f",
|
"url": "https://shigusegubu.club/objects/145d5252-7b8e-467d-9f36-1db0818f452f",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -1252,7 +1306,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://pleroma.site/objects/3076c055-0e34-4cf9-86ca-2d148b9b694a",
|
"uri": "https://pleroma.site/objects/3076c055-0e34-4cf9-86ca-2d148b9b694a",
|
||||||
"url": "https://pleroma.site/objects/3076c055-0e34-4cf9-86ca-2d148b9b694a",
|
"url": "https://pleroma.site/objects/3076c055-0e34-4cf9-86ca-2d148b9b694a",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"reblogged": false,
|
"reblogged": false,
|
||||||
"reblogs_count": 0,
|
"reblogs_count": 0,
|
||||||
|
@ -1262,7 +1319,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://pleroma.site/objects/3076c055-0e34-4cf9-86ca-2d148b9b694a",
|
"uri": "https://pleroma.site/objects/3076c055-0e34-4cf9-86ca-2d148b9b694a",
|
||||||
"url": "https://pleroma.site/objects/3076c055-0e34-4cf9-86ca-2d148b9b694a",
|
"url": "https://pleroma.site/objects/3076c055-0e34-4cf9-86ca-2d148b9b694a",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -1323,7 +1383,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/d4eb7c46-02f9-4b1f-83af-926cefa21f33",
|
"uri": "https://shigusegubu.club/objects/d4eb7c46-02f9-4b1f-83af-926cefa21f33",
|
||||||
"url": "https://shigusegubu.club/objects/d4eb7c46-02f9-4b1f-83af-926cefa21f33",
|
"url": "https://shigusegubu.club/objects/d4eb7c46-02f9-4b1f-83af-926cefa21f33",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -1446,7 +1509,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://pleroma.soykaf.com/objects/338b6bd2-3c2d-40fe-93a3-28b688782733",
|
"uri": "https://pleroma.soykaf.com/objects/338b6bd2-3c2d-40fe-93a3-28b688782733",
|
||||||
"url": "https://pleroma.soykaf.com/objects/338b6bd2-3c2d-40fe-93a3-28b688782733",
|
"url": "https://pleroma.soykaf.com/objects/338b6bd2-3c2d-40fe-93a3-28b688782733",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"reblogged": false,
|
"reblogged": false,
|
||||||
"reblogs_count": 0,
|
"reblogs_count": 0,
|
||||||
|
@ -1456,7 +1522,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://pleroma.soykaf.com/objects/338b6bd2-3c2d-40fe-93a3-28b688782733",
|
"uri": "https://pleroma.soykaf.com/objects/338b6bd2-3c2d-40fe-93a3-28b688782733",
|
||||||
"url": "https://pleroma.soykaf.com/objects/338b6bd2-3c2d-40fe-93a3-28b688782733",
|
"url": "https://pleroma.soykaf.com/objects/338b6bd2-3c2d-40fe-93a3-28b688782733",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -1517,7 +1586,10 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/f472f4ed-8b0b-492f-9d53-d69eda79629d",
|
"uri": "https://shigusegubu.club/objects/f472f4ed-8b0b-492f-9d53-d69eda79629d",
|
||||||
"url": "https://shigusegubu.club/objects/f472f4ed-8b0b-492f-9d53-d69eda79629d",
|
"url": "https://shigusegubu.club/objects/f472f4ed-8b0b-492f-9d53-d69eda79629d",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
"account": {
|
"account": {
|
||||||
"acct": "hj",
|
"acct": "hj",
|
||||||
|
@ -1578,5 +1650,8 @@
|
||||||
"tags": [],
|
"tags": [],
|
||||||
"uri": "https://shigusegubu.club/objects/d6fb4fd2-1f6a-4446-a1a6-5edd34050096",
|
"uri": "https://shigusegubu.club/objects/d6fb4fd2-1f6a-4446-a1a6-5edd34050096",
|
||||||
"url": "https://shigusegubu.club/objects/d6fb4fd2-1f6a-4446-a1a6-5edd34050096",
|
"url": "https://shigusegubu.club/objects/d6fb4fd2-1f6a-4446-a1a6-5edd34050096",
|
||||||
"visibility": "public"
|
"visibility": "public",
|
||||||
|
"pleroma": {
|
||||||
|
"local": true
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -129,7 +129,10 @@ const makeMockStatusMasto = (overrides = {}) => {
|
||||||
tags: [],
|
tags: [],
|
||||||
uri: 'https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639',
|
uri: 'https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639',
|
||||||
url: 'https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639',
|
url: 'https://shigusegubu.club/objects/16033fbb-97c0-4f0e-b834-7abb92fb8639',
|
||||||
visibility: 'public'
|
visibility: 'public',
|
||||||
|
pleroma: {
|
||||||
|
local: true
|
||||||
|
}
|
||||||
}, overrides)
|
}, overrides)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,14 +9,17 @@ describe('NotificationUtils', () => {
|
||||||
notifications: {
|
notifications: {
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
|
id: 1,
|
||||||
action: { id: '1' },
|
action: { id: '1' },
|
||||||
type: 'like'
|
type: 'like'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 2,
|
||||||
action: { id: '2' },
|
action: { id: '2' },
|
||||||
type: 'mention'
|
type: 'mention'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
id: 3,
|
||||||
action: { id: '3' },
|
action: { id: '3' },
|
||||||
type: 'repeat'
|
type: 'repeat'
|
||||||
}
|
}
|
||||||
|
@ -35,10 +38,12 @@ describe('NotificationUtils', () => {
|
||||||
const expected = [
|
const expected = [
|
||||||
{
|
{
|
||||||
action: { id: '3' },
|
action: { id: '3' },
|
||||||
|
id: 3,
|
||||||
type: 'repeat'
|
type: 'repeat'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
action: { id: '1' },
|
action: { id: '1' },
|
||||||
|
id: 1,
|
||||||
type: 'like'
|
type: 'like'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue