wip
This commit is contained in:
parent
7d4ced15c6
commit
15cc5f44e9
15 changed files with 85 additions and 65 deletions
|
|
@ -123,7 +123,7 @@ export const promisedRequest = async ({
|
||||||
const { ok, status } = response
|
const { ok, status } = response
|
||||||
|
|
||||||
if (ok) {
|
if (ok) {
|
||||||
return { response, status, data }
|
return { response, status, data, timestamp: Date.now() }
|
||||||
} else {
|
} else {
|
||||||
throw new StatusCodeError(response.status, data, { url, options }, response)
|
throw new StatusCodeError(response.status, data, { url, options }, response)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -370,11 +370,12 @@ const Chat = {
|
||||||
async startFetching() {
|
async startFetching() {
|
||||||
if (!this.isConversation) {
|
if (!this.isConversation) {
|
||||||
try {
|
try {
|
||||||
const { data } = await getOrCreateChat({
|
const result = await getOrCreateChat({
|
||||||
accountId: this.chatUserId,
|
accountId: this.chatUserId,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
})
|
})
|
||||||
this.$store.commit('addNewUsers', [data.account])
|
useUsersStore().addNewUsers(result)
|
||||||
|
const { data } = result
|
||||||
data.account = useUsersStore().findUser(data.account.id)
|
data.account = useUsersStore().findUser(data.account.id)
|
||||||
this.chat = data
|
this.chat = data
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ const ListsNew = {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.membersUserIds = this.findListAccounts(this.id)
|
this.membersUserIds = this.findListAccounts(this.id)
|
||||||
this.membersUserIds.forEach((userId) => {
|
this.membersUserIds.forEach((userId) => {
|
||||||
this.$store.dispatch('fetchUserIfMissing', userId)
|
useUsersStore().fetchUserIfMissing(userId)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||||
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
import { fetchUser } from 'src/api/public.js'
|
import { fetchUser } from 'src/api/public.js'
|
||||||
|
|
||||||
|
|
@ -16,17 +17,14 @@ const RemoteUserResolver = {
|
||||||
id,
|
id,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
})
|
})
|
||||||
.then(({ data: externalUser }) => {
|
.then((result) => {
|
||||||
if (externalUser.error) {
|
const { data: externalUser } = result
|
||||||
this.error = true
|
useUsersStore().addNewUsers(result)
|
||||||
} else {
|
|
||||||
this.$store.commit('addNewUsers', [externalUser])
|
|
||||||
const id = externalUser.id
|
const id = externalUser.id
|
||||||
this.$router.replace({
|
this.$router.replace({
|
||||||
name: 'external-user-profile',
|
name: 'external-user-profile',
|
||||||
params: { id },
|
params: { id },
|
||||||
})
|
})
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
this.error = true
|
this.error = true
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
import { groupBy, map } from 'lodash'
|
import { groupBy, map } from 'lodash'
|
||||||
import { mapGetters, mapState } from 'vuex'
|
import { mapState } from 'pinia'
|
||||||
|
|
||||||
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
|
import BasicUserCard from 'src/components/basic_user_card/basic_user_card.vue'
|
||||||
|
|
||||||
import { useInstanceStore } from 'src/stores/instance.js'
|
import { useInstanceStore } from 'src/stores/instance.js'
|
||||||
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
const StaffPanel = {
|
const StaffPanel = {
|
||||||
created() {
|
created() {
|
||||||
const nicknames = useInstanceStore().staffAccounts
|
const nicknames = useInstanceStore().staffAccounts
|
||||||
nicknames.forEach((nickname) =>
|
nicknames.forEach((nickname) =>
|
||||||
this.$store.dispatch('fetchUserIfMissing', nickname),
|
useUsersStore().fetchUserIfMissing(nickname),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
@ -27,10 +28,8 @@ const StaffPanel = {
|
||||||
{ role: 'moderator', users: groupedStaffAccounts.moderator },
|
{ role: 'moderator', users: groupedStaffAccounts.moderator },
|
||||||
].filter((group) => group.users)
|
].filter((group) => group.users)
|
||||||
},
|
},
|
||||||
...mapGetters(['findUserByName']),
|
...mapState(useUsersStore, ['findUserByName']),
|
||||||
...mapState({
|
...mapState(useInstanceStore, ['staffAccounts']),
|
||||||
staffAccounts: (state) => useInstanceStore().staffAccounts,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -174,9 +174,7 @@ const Status = {
|
||||||
},
|
},
|
||||||
replyProfileLink() {
|
replyProfileLink() {
|
||||||
if (this.isReply) {
|
if (this.isReply) {
|
||||||
const user = this.$store.getters.findUser(
|
const user = useUsersStore().findUser(this.status.in_reply_to_user_id)
|
||||||
this.status.in_reply_to_user_id,
|
|
||||||
)
|
|
||||||
// FIXME Why user not found sometimes???
|
// FIXME Why user not found sometimes???
|
||||||
return user ? user.statusnet_profile_url : 'NOT_FOUND'
|
return user ? user.statusnet_profile_url : 'NOT_FOUND'
|
||||||
}
|
}
|
||||||
|
|
@ -371,9 +369,7 @@ const Status = {
|
||||||
if (this.status.in_reply_to_screen_name) {
|
if (this.status.in_reply_to_screen_name) {
|
||||||
return this.status.in_reply_to_screen_name
|
return this.status.in_reply_to_screen_name
|
||||||
} else {
|
} else {
|
||||||
const user = this.$store.getters.findUser(
|
const user = useUsersStore().findUser(this.status.in_reply_to_user_id)
|
||||||
this.status.in_reply_to_user_id,
|
|
||||||
)
|
|
||||||
return user?.screen_name_ui
|
return user?.screen_name_ui
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { mapState } from 'pinia'
|
||||||
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
import RichContent from 'src/components/rich_content/rich_content.jsx'
|
||||||
|
|
||||||
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
import { useMergedConfigStore } from 'src/stores/merged_config.js'
|
||||||
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||||
import {
|
import {
|
||||||
|
|
@ -154,7 +155,7 @@ const StatusBody = {
|
||||||
mounted() {
|
mounted() {
|
||||||
this.status.attentions?.forEach((attn) => {
|
this.status.attentions?.forEach((attn) => {
|
||||||
const { id } = attn
|
const { id } = attn
|
||||||
this.$store.dispatch('fetchUserIfMissing', id)
|
useUsersStore().fetchUserIfMissing(id)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -171,7 +172,7 @@ const StatusBody = {
|
||||||
if (!cleanedString.startsWith('@')) return
|
if (!cleanedString.startsWith('@')) return
|
||||||
const handle = cleanedString.slice(1)
|
const handle = cleanedString.slice(1)
|
||||||
const host = url.replace(/^https?:\/\//, '').replace(/\/.+?$/, '')
|
const host = url.replace(/^https?:\/\//, '').replace(/\/.+?$/, '')
|
||||||
this.$store.dispatch('fetchUserIfMissing', `${handle}@${host}`)
|
useUsersStore().fetchUserIfMissing(`${handle}@${host}`)
|
||||||
})
|
})
|
||||||
/* This is a bit of a hack to make current tall status detector work
|
/* This is a bit of a hack to make current tall status detector work
|
||||||
* with rich mentions. Invisible mentions are detected at RichContent level
|
* with rich mentions. Invisible mentions are detected at RichContent level
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const UserProfileAdminView = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$store.dispatch('fetchUserIfMissing', this.userId)
|
useUsersStore().fetchUserIfMissing(this.userId)
|
||||||
useInterfaceStore().setForeignProfileBackground(this.user?.background_image)
|
useInterfaceStore().setForeignProfileBackground(this.user?.background_image)
|
||||||
},
|
},
|
||||||
updated() {
|
updated() {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import FollowCard from 'src/components/follow_card/follow_card.vue'
|
import FollowCard from 'src/components/follow_card/follow_card.vue'
|
||||||
|
|
||||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||||
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
import { fetchUser, suggestions } from 'src/api/public.js'
|
import { fetchUser, suggestions } from 'src/api/public.js'
|
||||||
|
|
||||||
|
|
@ -22,9 +23,10 @@ const WhoToFollow = {
|
||||||
fetchUser({
|
fetchUser({
|
||||||
id,
|
id,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then(({ data: externalUser }) => {
|
}).then((result) => {
|
||||||
|
const { data: externalUser } = result
|
||||||
if (!externalUser.error) {
|
if (!externalUser.error) {
|
||||||
this.$store.commit('addNewUsers', [externalUser])
|
useUsersStore().addNewUsers(result)
|
||||||
this.users.push(externalUser)
|
this.users.push(externalUser)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,10 @@ function showWhoToFollow(panel, reply) {
|
||||||
fetchUser({
|
fetchUser({
|
||||||
id: name,
|
id: name,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then(({ data: externalUser }) => {
|
}).then((result) => {
|
||||||
if (!externalUser.error) {
|
const { data: externalUser } = result
|
||||||
panel.$store.commit('addNewUsers', [externalUser])
|
useUsersStore().addNewUsers(result)
|
||||||
toFollow.id = externalUser.id
|
toFollow.id = externalUser.id
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ const i18n = createI18n({
|
||||||
messages.setLanguage(i18n.global, currentLocale)
|
messages.setLanguage(i18n.global, currentLocale)
|
||||||
|
|
||||||
const persistedStateOptions = {
|
const persistedStateOptions = {
|
||||||
paths: ['users.lastLoginName', 'oauth', 'config'],
|
paths: ['oauth', 'config'],
|
||||||
}
|
}
|
||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
|
|
|
||||||
|
|
@ -876,12 +876,16 @@ const statuses = {
|
||||||
following,
|
following,
|
||||||
type,
|
type,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then(({ data }) => {
|
}).then((result) => {
|
||||||
store.commit('addNewUsers', data.accounts)
|
const { data, ...rest } = result
|
||||||
store.commit(
|
useUsersStore().addNewUsers({
|
||||||
'addNewUsers',
|
...rest,
|
||||||
data.statuses.map((s) => s.user).filter(Boolean),
|
data: data.accounts,
|
||||||
)
|
})
|
||||||
|
useUsersStore().addNewUsers({
|
||||||
|
...rest,
|
||||||
|
data: data.statuses.map((s) => s.user).filter(Boolean),
|
||||||
|
})
|
||||||
store.commit('addNewStatuses', {
|
store.commit('addNewStatuses', {
|
||||||
statuses: data.statuses,
|
statuses: data.statuses,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
import { fetchFollowRequests } from 'src/api/user.js'
|
import { fetchFollowRequests } from 'src/api/user.js'
|
||||||
import { promiseInterval } from 'src/services/promise_interval/promise_interval.js'
|
import { promiseInterval } from 'src/services/promise_interval/promise_interval.js'
|
||||||
|
|
||||||
const fetchAndUpdate = ({ store, credentials }) => {
|
const fetchAndUpdate = ({ store, credentials }) => {
|
||||||
return fetchFollowRequests({ credentials })
|
return fetchFollowRequests({ credentials })
|
||||||
.then(
|
.then(
|
||||||
({ data: requests }) => {
|
(result) => {
|
||||||
|
const { data: requests } = result
|
||||||
store.commit('setFollowRequests', requests)
|
store.commit('setFollowRequests', requests)
|
||||||
store.commit('addNewUsers', requests)
|
useUsersStore().addNewUsers(result)
|
||||||
},
|
},
|
||||||
(rej) => {
|
(rej) => {
|
||||||
console.error(rej)
|
console.error(rej)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js'
|
||||||
import { promiseInterval } from '../services/promise_interval/promise_interval.js'
|
import { promiseInterval } from '../services/promise_interval/promise_interval.js'
|
||||||
|
|
||||||
import { useOAuthStore } from 'src/stores/oauth.js'
|
import { useOAuthStore } from 'src/stores/oauth.js'
|
||||||
|
import { useUsersStore } from 'src/stores/users.js'
|
||||||
|
|
||||||
import { chats } from 'src/api/chats.js'
|
import { chats } from 'src/api/chats.js'
|
||||||
|
|
||||||
|
|
@ -41,11 +42,11 @@ export const useChatsStore = defineStore('chats', {
|
||||||
this.setChatListFetcher(null)
|
this.setChatListFetcher(null)
|
||||||
},
|
},
|
||||||
async fetchChats() {
|
async fetchChats() {
|
||||||
const { data } = await chats({
|
this.addNewChats(
|
||||||
|
await chats({
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
})
|
}),
|
||||||
|
)
|
||||||
this.addNewChats(data)
|
|
||||||
},
|
},
|
||||||
setChatListFetcher(fetcher) {
|
setChatListFetcher(fetcher) {
|
||||||
const prevFetcher = this.chatListFetcher
|
const prevFetcher = this.chatListFetcher
|
||||||
|
|
@ -58,11 +59,11 @@ export const useChatsStore = defineStore('chats', {
|
||||||
this.chatList = emptyChatList()
|
this.chatList = emptyChatList()
|
||||||
this.setChatListFetcher(null)
|
this.setChatListFetcher(null)
|
||||||
},
|
},
|
||||||
addNewChats(chats) {
|
addNewChats(result) {
|
||||||
window.vuex.commit(
|
useUsersStore().addNewUsers({
|
||||||
'addNewUsers',
|
...result,
|
||||||
chats.map((k) => k.account).filter(Boolean),
|
data: result.data.map((k) => k.account).filter(Boolean),
|
||||||
)
|
})
|
||||||
|
|
||||||
chats.forEach((updatedChat) => {
|
chats.forEach((updatedChat) => {
|
||||||
const chat = getChatById(this, updatedChat.id)
|
const chat = getChatById(this, updatedChat.id)
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
usersByName: new Map(),
|
usersByName: new Map(),
|
||||||
usersByURL: new Map(),
|
usersByURL: new Map(),
|
||||||
relationships: new Map(),
|
relationships: new Map(),
|
||||||
|
timestamps: new WeakMap(),
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
loggedIn: (state) => !!state.currentUser,
|
loggedIn: (state) => !!state.currentUser,
|
||||||
|
|
@ -137,9 +138,16 @@ export const useUsersStore = defineStore('users', {
|
||||||
user.followerIds = []
|
user.followerIds = []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addNewUsers(users, timestamp) {
|
addNewUsers(response) {
|
||||||
|
const { data, timestamp } = response
|
||||||
|
const users = Array.isArray(data) ? data : [data]
|
||||||
|
|
||||||
users.forEach((user) => {
|
users.forEach((user) => {
|
||||||
const existing = this.users.get(user.id) ?? {}
|
const existing = this.users.get(user.id) ?? {}
|
||||||
|
const oldTimestamp = this.timestamps.get(existing)
|
||||||
|
|
||||||
|
// implicit: if oldTimestamp is undefined this will still be false
|
||||||
|
if (oldTimestamp > timestamp) return // not overwriting old data with new
|
||||||
|
|
||||||
const { relationship, ...old } = existing
|
const { relationship, ...old } = existing
|
||||||
const { relationshop, ...neu } = user
|
const { relationshop, ...neu } = user
|
||||||
|
|
@ -148,6 +156,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
this.users.set(user.id, newUser)
|
this.users.set(user.id, newUser)
|
||||||
this.usersByName.set(user.screen_name.toLowerCase(), newUser)
|
this.usersByName.set(user.screen_name.toLowerCase(), newUser)
|
||||||
this.usersByURL.set(user.url.toLowerCase(), newUser)
|
this.usersByURL.set(user.url.toLowerCase(), newUser)
|
||||||
|
this.timestamps.set(newUser, timestamp)
|
||||||
|
|
||||||
if (user.id === this.currentUser.id) {
|
if (user.id === this.currentUser.id) {
|
||||||
this.currentUser = newUser
|
this.currentUser = newUser
|
||||||
|
|
@ -277,7 +286,8 @@ export const useUsersStore = defineStore('users', {
|
||||||
return fetchBlocks({
|
return fetchBlocks({
|
||||||
maxId,
|
maxId,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then(({ data: blocks }) => {
|
}).then((result) => {
|
||||||
|
const { data: blocks } = result
|
||||||
if (reset) {
|
if (reset) {
|
||||||
this.saveBlockIds(blocks.map(({ id }) => id))
|
this.saveBlockIds(blocks.map(({ id }) => id))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -286,7 +296,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
if (blocks.length) {
|
if (blocks.length) {
|
||||||
this.setBlockIdsMaxId(last(blocks).id)
|
this.setBlockIdsMaxId(last(blocks).id)
|
||||||
}
|
}
|
||||||
this.addNewUsers(blocks)
|
this.addNewUsers(result)
|
||||||
return blocks
|
return blocks
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -337,7 +347,8 @@ export const useUsersStore = defineStore('users', {
|
||||||
return fetchMutes({
|
return fetchMutes({
|
||||||
maxId,
|
maxId,
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
}).then(({ data: mutes }) => {
|
}).then((result) => {
|
||||||
|
const { data: mutes } = result
|
||||||
if (reset) {
|
if (reset) {
|
||||||
this.saveMuteIds(mutes.map(({ id }) => id))
|
this.saveMuteIds(mutes.map(({ id }) => id))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -346,7 +357,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
if (mutes.length) {
|
if (mutes.length) {
|
||||||
this.setMuteIdsMaxId(last(mutes).id)
|
this.setMuteIdsMaxId(last(mutes).id)
|
||||||
}
|
}
|
||||||
this.addNewUsers(mutes)
|
this.addNewUsers(result)
|
||||||
return mutes
|
return mutes
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -541,7 +552,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
verifyCredentials({
|
verifyCredentials({
|
||||||
credentials: useOAuthStore().token,
|
credentials: useOAuthStore().token,
|
||||||
})
|
})
|
||||||
.then(({ data: user }) => {
|
.then(({ data: user, ...rest }) => {
|
||||||
// user.credentials = userCredentials
|
// user.credentials = userCredentials
|
||||||
user.credentials = accessToken
|
user.credentials = accessToken
|
||||||
user.blockIds = []
|
user.blockIds = []
|
||||||
|
|
@ -559,7 +570,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
useUserHighlightStore().initUserHighlight(user)
|
useUserHighlightStore().initUserHighlight(user)
|
||||||
this.addNewUsers([user])
|
this.addNewUsers({ data: user, ...rest })
|
||||||
|
|
||||||
useEmojiStore().fetchEmoji()
|
useEmojiStore().fetchEmoji()
|
||||||
|
|
||||||
|
|
@ -598,7 +609,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
|
|
||||||
if (useInstanceCapabilitiesStore().pleromaChatMessagesAvailable) {
|
if (useInstanceCapabilitiesStore().pleromaChatMessagesAvailable) {
|
||||||
// Start fetching chats
|
// Start fetching chats
|
||||||
dispatch('startFetchingChats')
|
useChatsStore().startFetchingChats()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -643,7 +654,7 @@ export const useUsersStore = defineStore('users', {
|
||||||
useInterfaceStore().setLayoutHeight(windowHeight())
|
useInterfaceStore().setLayoutHeight(windowHeight())
|
||||||
|
|
||||||
// Fetch our friends
|
// Fetch our friends
|
||||||
fetchFriends({ id: user.id }).then(({ data: friends }) =>
|
fetchFriends({ id: user.id }).then((friends) =>
|
||||||
this.addNewUsers(friends),
|
this.addNewUsers(friends),
|
||||||
)
|
)
|
||||||
this.loggingIn = false
|
this.loggingIn = false
|
||||||
|
|
@ -670,4 +681,9 @@ export const useUsersStore = defineStore('users', {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
persist: {
|
||||||
|
afterLoad({ lastLoginName }) {
|
||||||
|
return { lastLoginName }
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue