Merge pull request #875 from h3poteto/iss-850
refs #850 Replace SideBar with typescript
This commit is contained in:
commit
c0df6001fe
|
@ -4,7 +4,7 @@ import Mastodon, { Account, Emoji, Instance, Status, Notification as Notificatio
|
|||
import SideMenu, { SideMenuState } from './TimelineSpace/SideMenu'
|
||||
import HeaderMenu, { HeaderMenuState } from './TimelineSpace/HeaderMenu'
|
||||
import Modals, { ModalsModuleState } from './TimelineSpace/Modals'
|
||||
import Contents from './TimelineSpace/Contents'
|
||||
import Contents, { ContentsModuleState } from './TimelineSpace/Contents'
|
||||
import router from '@/router'
|
||||
import unreadSettings from '~/src/constants/unreadNotification'
|
||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
|
@ -419,8 +419,8 @@ const actions: ActionTree<TimelineSpaceState, any> = {
|
|||
export interface TimelineSpaceModuleState extends TimelineSpaceState {
|
||||
SideMenu: SideMenuState,
|
||||
HeaderMenu: HeaderMenuState,
|
||||
Modals: ModalsModuleState
|
||||
// TODO: Contents: ContentsState
|
||||
Modals: ModalsModuleState,
|
||||
Contents: ContentsModuleState
|
||||
}
|
||||
|
||||
const TimelineSpace: Module<TimelineSpaceState, any> = {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import SideBar from './Contents/SideBar'
|
||||
import SideBar, { SideBarModuleState } from './Contents/SideBar'
|
||||
import Home from './Contents/Home'
|
||||
import Notifications from './Contents/Notifications'
|
||||
import Favourites from './Contents/Favourites'
|
||||
|
@ -9,9 +9,20 @@ import Lists from './Contents/Lists'
|
|||
import Hashtag from './Contents/Hashtag'
|
||||
import DirectMessages from './Contents/DirectMessages'
|
||||
import Mentions from './Contents/Mentions'
|
||||
import { Module } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const Contents = {
|
||||
export interface ContentsState {}
|
||||
|
||||
export interface ContentsModuleState extends ContentsState {
|
||||
SideBar: SideBarModuleState
|
||||
}
|
||||
|
||||
const state = (): ContentsState => ({})
|
||||
|
||||
const Contents: Module<ContentsState, RootState> = {
|
||||
namespaced: true,
|
||||
state: state,
|
||||
modules: {
|
||||
SideBar,
|
||||
Home,
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import AccountProfile from './SideBar/AccountProfile'
|
||||
import TootDetail from './SideBar/TootDetail'
|
||||
|
||||
const SideBar = {
|
||||
namespaced: true,
|
||||
modules: {
|
||||
AccountProfile,
|
||||
TootDetail
|
||||
},
|
||||
state: {
|
||||
openSideBar: false,
|
||||
// 0: blank
|
||||
// 1: account-profile
|
||||
// 2: toot-detail
|
||||
component: 0
|
||||
},
|
||||
mutations: {
|
||||
changeOpenSideBar (state, value) {
|
||||
state.openSideBar = value
|
||||
},
|
||||
changeComponent (state, value) {
|
||||
state.component = value
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
close ({ dispatch, commit }) {
|
||||
dispatch('TimelineSpace/Contents/SideBar/AccountProfile/close', {}, { root: true })
|
||||
commit('changeOpenSideBar', false)
|
||||
commit('changeComponent', 0)
|
||||
},
|
||||
openAccountComponent ({ commit }) {
|
||||
commit('changeComponent', 1)
|
||||
},
|
||||
openTootComponent ({ commit }) {
|
||||
commit('changeComponent', 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SideBar
|
|
@ -0,0 +1,63 @@
|
|||
import AccountProfile, { AccountProfileModuleState } from './SideBar/AccountProfile'
|
||||
import TootDetail, { TootDetailState } from './SideBar/TootDetail'
|
||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
export interface SideBarState {
|
||||
openSideBar: boolean,
|
||||
// 0: blank
|
||||
// 1: account-profile
|
||||
// 2: toot-detail
|
||||
component: number
|
||||
}
|
||||
|
||||
export interface SideBarModuleState extends SideBarState {
|
||||
TootDetail: TootDetailState,
|
||||
AccountProfile: AccountProfileModuleState
|
||||
}
|
||||
|
||||
const state = (): SideBarState => ({
|
||||
openSideBar: false,
|
||||
component: 0
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
CHANGE_OPEN_SIDEBAR: 'changeOpenSideBar',
|
||||
CHANGE_COMPONENT: 'changeComponent'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<SideBarState> = {
|
||||
[MUTATION_TYPES.CHANGE_OPEN_SIDEBAR]: (state, value: boolean) => {
|
||||
state.openSideBar = value
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_COMPONENT]: (state, value: number) => {
|
||||
state.component = value
|
||||
}
|
||||
}
|
||||
|
||||
const actions: ActionTree<SideBarState, RootState> = {
|
||||
close: ({ dispatch, commit }) => {
|
||||
dispatch('TimelineSpace/Contents/SideBar/AccountProfile/close', {}, { root: true })
|
||||
commit(MUTATION_TYPES.CHANGE_OPEN_SIDEBAR, false)
|
||||
commit(MUTATION_TYPES.CHANGE_COMPONENT, 0)
|
||||
},
|
||||
openAccountComponent: ({ commit }) => {
|
||||
commit(MUTATION_TYPES.CHANGE_COMPONENT, 1)
|
||||
},
|
||||
openTootComponent: ({ commit }) => {
|
||||
commit(MUTATION_TYPES.CHANGE_COMPONENT, 2)
|
||||
}
|
||||
}
|
||||
|
||||
const SideBar: Module<SideBarState, RootState> = {
|
||||
namespaced: true,
|
||||
modules: {
|
||||
AccountProfile,
|
||||
TootDetail
|
||||
},
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
actions: actions
|
||||
}
|
||||
|
||||
export default SideBar
|
|
@ -1,140 +0,0 @@
|
|||
import Mastodon from 'megalodon'
|
||||
import Timeline from './AccountProfile/Timeline'
|
||||
import Follows from './AccountProfile/Follows'
|
||||
import Followers from './AccountProfile/Followers'
|
||||
|
||||
const AccountProfile = {
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Timeline,
|
||||
Follows,
|
||||
Followers
|
||||
},
|
||||
state: {
|
||||
account: null,
|
||||
relationship: null,
|
||||
loading: false
|
||||
},
|
||||
mutations: {
|
||||
changeAccount (state, account) {
|
||||
state.account = account
|
||||
},
|
||||
changeRelationship (state, relationship) {
|
||||
state.relationship = relationship
|
||||
},
|
||||
changeLoading (state, value) {
|
||||
state.loading = value
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
fetchAccount ({ rootState }, accountID) {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.get(`/accounts/${accountID}`)
|
||||
.then(res => res.data)
|
||||
},
|
||||
searchAccount ({ rootState }, parsedAccount) {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.get('/search', { q: parsedAccount.url, resolve: true })
|
||||
.then(res => {
|
||||
if (res.data.accounts.length <= 0) throw new AccountNotFound('empty result')
|
||||
const account = res.data.accounts.find(a => `@${a.acct}` === parsedAccount.acct)
|
||||
if (account) return account
|
||||
const pleromaUser = res.data.accounts.find(a => a.acct === parsedAccount.acct)
|
||||
if (pleromaUser) return pleromaUser
|
||||
const localUser = res.data.accounts.find(a => `@${a.username}@${rootState.TimelineSpace.account.domain}` === parsedAccount.acct)
|
||||
if (localUser) return localUser
|
||||
const user = res.data.accounts.find(a => a.url === parsedAccount.url)
|
||||
if (!user) throw new AccountNotFound('not found')
|
||||
return user
|
||||
})
|
||||
},
|
||||
changeAccount ({ commit, dispatch }, account) {
|
||||
dispatch('fetchRelationship', account)
|
||||
commit('changeAccount', account)
|
||||
},
|
||||
fetchRelationship ({ commit, rootState }, account) {
|
||||
commit('changeRelationship', null)
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.get('/accounts/relationships', { id: [account.id] })
|
||||
.then(res => {
|
||||
commit('changeRelationship', res.data[0])
|
||||
return res.data
|
||||
})
|
||||
},
|
||||
follow ({ commit, rootState }, account) {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.post(`/accounts/${account.id}/follow`)
|
||||
.then(res => {
|
||||
commit('changeRelationship', res.data)
|
||||
return res.data
|
||||
})
|
||||
},
|
||||
unfollow ({ commit, rootState }, account) {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.post(`/accounts/${account.id}/unfollow`)
|
||||
.then(res => {
|
||||
commit('changeRelationship', res.data)
|
||||
return res.data
|
||||
})
|
||||
},
|
||||
close ({ commit }) {
|
||||
commit('changeAccount', null)
|
||||
},
|
||||
unmute ({ rootState, commit }, account) {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.post(`/accounts/${account.id}/unmute`)
|
||||
.then(res => {
|
||||
commit('changeRelationship', res.data)
|
||||
return res.data
|
||||
})
|
||||
},
|
||||
block ({ rootState, commit }, account) {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.post(`/accounts/${account.id}/block`)
|
||||
.then(res => {
|
||||
commit('changeRelationship', res.data)
|
||||
return res.data
|
||||
})
|
||||
},
|
||||
unblock ({ rootState, commit }, account) {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.post(`/accounts/${account.id}/unblock`)
|
||||
.then(res => {
|
||||
commit('changeRelationship', res.data)
|
||||
return res.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AccountNotFound {
|
||||
constructor (msg) {
|
||||
this.msg = msg
|
||||
}
|
||||
}
|
||||
|
||||
export default AccountProfile
|
|
@ -0,0 +1,148 @@
|
|||
import Mastodon, { Account, Relationship, Response, Results } from 'megalodon'
|
||||
import Timeline, { TimelineState } from './AccountProfile/Timeline'
|
||||
import Follows, { FollowsState } from './AccountProfile/Follows'
|
||||
import Followers, { FollowersState } from './AccountProfile/Followers'
|
||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
export interface AccountProfileState {
|
||||
account: Account | null,
|
||||
relationship: Relationship | null,
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
export interface AccountProfileModuleState extends AccountProfileState {
|
||||
Followers: FollowersState,
|
||||
Follows: FollowsState,
|
||||
Timeline: TimelineState
|
||||
}
|
||||
|
||||
const state = (): AccountProfileState => ({
|
||||
account: null,
|
||||
relationship: null,
|
||||
loading: false
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
CHANGE_ACCOUNT: 'changeAccount',
|
||||
CHANGE_RELATIONSHIP: 'changeRelationship',
|
||||
CHANGE_LOADING: 'changeLoading'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<AccountProfileState> = {
|
||||
[MUTATION_TYPES.CHANGE_ACCOUNT]: (state, account: Account | null) => {
|
||||
state.account = account
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_RELATIONSHIP]: (state, relationship: Relationship | null) => {
|
||||
state.relationship = relationship
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_LOADING]: (state, value: boolean) => {
|
||||
state.loading = value
|
||||
}
|
||||
}
|
||||
|
||||
const actions: ActionTree<AccountProfileState, RootState> = {
|
||||
fetchAccount: async ({ rootState }, accountID: number): Promise<Account> => {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Account> = await client.get<Account>(`/accounts/${accountID}`)
|
||||
return res.data
|
||||
},
|
||||
searchAccount: async ({ rootState }, parsedAccount): Promise<Account> => {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Results> = await client.get<Results>('/search', { q: parsedAccount.url, resolve: true })
|
||||
if (res.data.accounts.length <= 0) throw new AccountNotFound('empty result')
|
||||
const account = res.data.accounts.find(a => `@${a.acct}` === parsedAccount.acct)
|
||||
if (account) return account
|
||||
const pleromaUser = res.data.accounts.find(a => a.acct === parsedAccount.acct)
|
||||
if (pleromaUser) return pleromaUser
|
||||
const localUser = res.data.accounts.find(a => `@${a.username}@${rootState.TimelineSpace.account.domain}` === parsedAccount.acct)
|
||||
if (localUser) return localUser
|
||||
const user = res.data.accounts.find(a => a.url === parsedAccount.url)
|
||||
if (!user) throw new AccountNotFound('not found')
|
||||
return user
|
||||
},
|
||||
changeAccount: ({ commit, dispatch }, account: Account) => {
|
||||
dispatch('fetchRelationship', account)
|
||||
commit(MUTATION_TYPES.CHANGE_ACCOUNT, account)
|
||||
},
|
||||
fetchRelationship: async ({ commit, rootState }, account: Account): Promise<Relationship> => {
|
||||
commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, null)
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Relationship> = await client.get<Relationship>('/accounts/relationships', { id: [account.id] })
|
||||
commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data[0])
|
||||
return res.data
|
||||
},
|
||||
follow: async ({ commit, rootState }, account: Account) => {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Relationship> = await client.post<Relationship>(`/accounts/${account.id}/follow`)
|
||||
commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
|
||||
return res.data
|
||||
},
|
||||
unfollow: async ({ commit, rootState }, account: Account) => {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Relationship> = await client.post<Relationship>(`/accounts/${account.id}/unfollow`)
|
||||
commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
|
||||
return res.data
|
||||
},
|
||||
close: ({ commit }) => {
|
||||
commit(MUTATION_TYPES.CHANGE_ACCOUNT, null)
|
||||
},
|
||||
unmute: async ({ rootState, commit }, account: Account) => {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Relationship> = await client.post<Relationship>(`/accounts/${account.id}/unmute`)
|
||||
commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
|
||||
return res.data
|
||||
},
|
||||
block: async ({ rootState, commit }, account: Account) => {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Relationship> = await client.post<Relationship>(`/accounts/${account.id}/block`)
|
||||
commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
|
||||
return res.data
|
||||
},
|
||||
unblock: async ({ rootState, commit }, account: Account) => {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Relationship> = await client.post<Relationship>(`/accounts/${account.id}/unblock`)
|
||||
commit(MUTATION_TYPES.CHANGE_RELATIONSHIP, res.data)
|
||||
return res.data
|
||||
}
|
||||
}
|
||||
|
||||
const AccountProfile: Module<AccountProfileState, RootState> = {
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Timeline,
|
||||
Follows,
|
||||
Followers
|
||||
},
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
actions: actions
|
||||
}
|
||||
|
||||
class AccountNotFound extends Error {}
|
||||
|
||||
export default AccountProfile
|
|
@ -1,46 +0,0 @@
|
|||
import Mastodon from 'megalodon'
|
||||
|
||||
const Followers = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
followers: [],
|
||||
relationships: []
|
||||
},
|
||||
mutations: {
|
||||
updateFollowers (state, users) {
|
||||
state.followers = users
|
||||
},
|
||||
updateRelationships (state, relations) {
|
||||
state.relationships = relations
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
fetchFollowers ({ commit, rootState }, account) {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.get(`/accounts/${account.id}/followers`, { limit: 80 })
|
||||
.then(res => {
|
||||
commit('updateFollowers', res.data)
|
||||
return res.data
|
||||
})
|
||||
},
|
||||
fetchRelationships ({ commit, rootState }, accounts) {
|
||||
const ids = accounts.map(a => a.id)
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.get(`/accounts/relationships`, {
|
||||
id: ids
|
||||
})
|
||||
.then(res => {
|
||||
commit('updateRelationships', res.data)
|
||||
return res.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Followers
|
|
@ -0,0 +1,60 @@
|
|||
import Mastodon, { Account, Relationship, Response } from 'megalodon'
|
||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
export interface FollowersState {
|
||||
followers: Array<Account>,
|
||||
relationships: Array<Relationship>
|
||||
}
|
||||
|
||||
const state = (): FollowersState => ({
|
||||
followers: [],
|
||||
relationships: []
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
UPDATE_FOLLOWERS: 'updateFollowers',
|
||||
UPDATE_RELATIONSHIPS: 'updateRelationships'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<FollowersState> = {
|
||||
[MUTATION_TYPES.UPDATE_FOLLOWERS]: (state, users: Array<Account>) => {
|
||||
state.followers = users
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_RELATIONSHIPS]: (state, relations: Array<Relationship>) => {
|
||||
state.relationships = relations
|
||||
}
|
||||
}
|
||||
|
||||
const actions: ActionTree<FollowersState, RootState> = {
|
||||
fetchFollowers: async ({ commit, rootState }, account: Account) => {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Array<Account>> = await client.get<Array<Account>>(`/accounts/${account.id}/followers`, { limit: 80 })
|
||||
commit(MUTATION_TYPES.UPDATE_FOLLOWERS, res.data)
|
||||
return res.data
|
||||
},
|
||||
fetchRelationships: async ({ commit, rootState }, accounts: Array<Account>) => {
|
||||
const ids = accounts.map(a => a.id)
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Array<Relationship>> = await client.get<Array<Relationship>>(`/accounts/relationships`, {
|
||||
id: ids
|
||||
})
|
||||
commit(MUTATION_TYPES.UPDATE_RELATIONSHIPS, res.data)
|
||||
return res.data
|
||||
}
|
||||
}
|
||||
|
||||
const Followers: Module<FollowersState, RootState> = {
|
||||
namespaced: true,
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
actions: actions
|
||||
}
|
||||
|
||||
export default Followers
|
|
@ -1,46 +0,0 @@
|
|||
import Mastodon from 'megalodon'
|
||||
|
||||
const Follows = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
follows: [],
|
||||
relationships: []
|
||||
},
|
||||
mutations: {
|
||||
updateFollows (state, users) {
|
||||
state.follows = users
|
||||
},
|
||||
updateRelationships (state, relations) {
|
||||
state.relationships = relations
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
fetchFollows ({ commit, rootState }, account) {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.get(`/accounts/${account.id}/following`, { limit: 80 })
|
||||
.then(res => {
|
||||
commit('updateFollows', res.data)
|
||||
return res.data
|
||||
})
|
||||
},
|
||||
fetchRelationships ({ commit, rootState }, accounts) {
|
||||
const ids = accounts.map(a => a.id)
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.get(`/accounts/relationships`, {
|
||||
id: ids
|
||||
})
|
||||
.then(res => {
|
||||
commit('updateRelationships', res.data)
|
||||
return res.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Follows
|
|
@ -0,0 +1,60 @@
|
|||
import Mastodon, { Account, Relationship, Response } from 'megalodon'
|
||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
export interface FollowsState {
|
||||
follows: Array<Account>,
|
||||
relationships: Array<Relationship>
|
||||
}
|
||||
|
||||
const state = (): FollowsState => ({
|
||||
follows: [],
|
||||
relationships: []
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
UPDATE_FOLLOWS: 'updateFollows',
|
||||
UPDATE_RELATIONSHIPS: 'updateRelationships'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<FollowsState> = {
|
||||
[MUTATION_TYPES.UPDATE_FOLLOWS]: (state, users: Array<Account>) => {
|
||||
state.follows = users
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_RELATIONSHIPS]: (state, relations: Array<Relationship>) => {
|
||||
state.relationships = relations
|
||||
}
|
||||
}
|
||||
|
||||
const actions: ActionTree<FollowsState, RootState> = {
|
||||
fetchFollows: async ({ commit, rootState }, account: Account) => {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Array<Account>> = await client.get<Array<Account>>(`/accounts/${account.id}/following`, { limit: 80 })
|
||||
commit(MUTATION_TYPES.UPDATE_FOLLOWS, res.data)
|
||||
return res.data
|
||||
},
|
||||
fetchRelationships: async ({ commit, rootState }, accounts: Array<Account>) => {
|
||||
const ids = accounts.map(a => a.id)
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Array<Relationship>> = await client.get<Array<Relationship>>(`/accounts/relationships`, {
|
||||
id: ids
|
||||
})
|
||||
commit(MUTATION_TYPES.UPDATE_RELATIONSHIPS, res.data)
|
||||
return res.data
|
||||
}
|
||||
}
|
||||
|
||||
const Follows: Module<FollowsState, RootState> = {
|
||||
namespaced: true,
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
actions: actions
|
||||
}
|
||||
|
||||
export default Follows
|
|
@ -1,116 +0,0 @@
|
|||
import Mastodon from 'megalodon'
|
||||
|
||||
const Timeline = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
timeline: [],
|
||||
pinnedToots: [],
|
||||
lazyLoading: false
|
||||
},
|
||||
mutations: {
|
||||
updateTimeline (state, timeline) {
|
||||
state.timeline = timeline
|
||||
},
|
||||
insertTimeline (state, messages) {
|
||||
state.timeline = state.timeline.concat(messages)
|
||||
},
|
||||
updatePinnedToots (state, messages) {
|
||||
state.pinnedToots = messages
|
||||
},
|
||||
changeLazyLoading (state, value) {
|
||||
state.lazyLoading = value
|
||||
},
|
||||
updatePinnedToot (state, message) {
|
||||
state.pinnedToots = state.pinnedToots.map((toot) => {
|
||||
if (toot.id === message.id) {
|
||||
return message
|
||||
} else if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
// When user reblog/favourite a reblogged toot, target message is a original toot.
|
||||
// So, a message which is received now is original toot.
|
||||
const reblog = {
|
||||
reblog: message
|
||||
}
|
||||
return Object.assign(toot, reblog)
|
||||
} else {
|
||||
return toot
|
||||
}
|
||||
})
|
||||
},
|
||||
updateToot (state, message) {
|
||||
// Replace target message in timeline
|
||||
state.timeline = state.timeline.map((toot) => {
|
||||
if (toot.id === message.id) {
|
||||
return message
|
||||
} else if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
// When user reblog/favourite a reblogged toot, target message is a original toot.
|
||||
// So, a message which is received now is original toot.
|
||||
const reblog = {
|
||||
reblog: message
|
||||
}
|
||||
return Object.assign(toot, reblog)
|
||||
} else {
|
||||
return toot
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteToot (state, message) {
|
||||
state.timeline = state.timeline.filter((toot) => {
|
||||
if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
return false
|
||||
} else {
|
||||
return toot.id !== message.id
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
fetchTimeline ({ commit, rootState }, account) {
|
||||
commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true, { root: true })
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.get(`/accounts/${account.id}/statuses`, { limit: 10, pinned: true })
|
||||
.then(res => {
|
||||
commit('updatePinnedToots', res.data)
|
||||
})
|
||||
.then(() => {
|
||||
return client.get(`/accounts/${account.id}/statuses`, { limit: 40 })
|
||||
})
|
||||
.then(res => {
|
||||
commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false, { root: true })
|
||||
commit('updateTimeline', res.data)
|
||||
return res.data
|
||||
})
|
||||
},
|
||||
lazyFetchTimeline ({ state, commit, rootState }, info) {
|
||||
const last = info.last
|
||||
if (last === undefined || last === null) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
if (state.lazyLoading) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
commit('changeLazyLoading', true)
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.get(`/accounts/${info.account.id}/statuses`, { max_id: last.id, limit: 40 })
|
||||
.then(res => {
|
||||
commit('changeLazyLoading', false)
|
||||
commit('insertTimeline', res.data)
|
||||
return res.data
|
||||
})
|
||||
.catch(err => {
|
||||
commit('changeLazyLoading', false)
|
||||
throw err
|
||||
})
|
||||
},
|
||||
clearTimeline ({ commit }) {
|
||||
commit('updateTimeline', [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Timeline
|
|
@ -0,0 +1,131 @@
|
|||
import Mastodon, { Status, Response } from 'megalodon'
|
||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
export interface TimelineState {
|
||||
timeline: Array<Status>,
|
||||
pinnedToots: Array<Status>,
|
||||
lazyLoading: boolean
|
||||
}
|
||||
|
||||
const state = (): TimelineState => ({
|
||||
timeline: [],
|
||||
pinnedToots: [],
|
||||
lazyLoading: false
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
UPDATE_TIMELINE: 'updateTimeline',
|
||||
INSERT_TIMELINE: 'insertTimeline',
|
||||
UPDATE_PINNED_TOOTS: 'updatePinnedToots',
|
||||
CHANGE_LAZY_LOADING: 'changeLazyLoading',
|
||||
UPDATE_PINNED_TOOT: 'updatePinnedToot',
|
||||
UPDATE_TOOT: 'updateToot',
|
||||
DELETE_TOOT: 'deleteToot'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<TimelineState> = {
|
||||
[MUTATION_TYPES.UPDATE_TIMELINE]: (state, timeline: Array<Status>) => {
|
||||
state.timeline = timeline
|
||||
},
|
||||
[MUTATION_TYPES.INSERT_TIMELINE]: (state, messages: Array<Status>) => {
|
||||
state.timeline = state.timeline.concat(messages)
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_PINNED_TOOTS]: (state, messages: Array<Status>) => {
|
||||
state.pinnedToots = messages
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_LAZY_LOADING]: (state, value: boolean) => {
|
||||
state.lazyLoading = value
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_PINNED_TOOT]: (state, message: Status) => {
|
||||
state.pinnedToots = state.pinnedToots.map((toot) => {
|
||||
if (toot.id === message.id) {
|
||||
return message
|
||||
} else if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
// When user reblog/favourite a reblogged toot, target message is a original toot.
|
||||
// So, a message which is received now is original toot.
|
||||
const reblog = {
|
||||
reblog: message
|
||||
}
|
||||
return Object.assign(toot, reblog)
|
||||
} else {
|
||||
return toot
|
||||
}
|
||||
})
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_TOOT]: (state, message: Status) => {
|
||||
// Replace target message in timeline
|
||||
state.timeline = state.timeline.map((toot) => {
|
||||
if (toot.id === message.id) {
|
||||
return message
|
||||
} else if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
// When user reblog/favourite a reblogged toot, target message is a original toot.
|
||||
// So, a message which is received now is original toot.
|
||||
const reblog = {
|
||||
reblog: message
|
||||
}
|
||||
return Object.assign(toot, reblog)
|
||||
} else {
|
||||
return toot
|
||||
}
|
||||
})
|
||||
},
|
||||
[MUTATION_TYPES.DELETE_TOOT]: (state, message: Status) => {
|
||||
state.timeline = state.timeline.filter((toot) => {
|
||||
if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
return false
|
||||
} else {
|
||||
return toot.id !== message.id
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const actions: ActionTree<TimelineState, RootState> = {
|
||||
fetchTimeline: async ({ commit, rootState }, account: Account) => {
|
||||
commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true, { root: true })
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const pinned: Response<Array<Status>> = await client.get<Array<Status>>(`/accounts/${account.id}/statuses`, { limit: 10, pinned: true })
|
||||
commit(MUTATION_TYPES.UPDATE_PINNED_TOOTS, pinned.data)
|
||||
const res: Response<Array<Status>> = await client.get<Array<Status>>(`/accounts/${account.id}/statuses`, { limit: 40 })
|
||||
commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false, { root: true })
|
||||
commit(MUTATION_TYPES.UPDATE_TIMELINE, res.data)
|
||||
return res.data
|
||||
},
|
||||
lazyFetchTimeline: async ({ state, commit, rootState }, info: any): Promise<null> => {
|
||||
const last = info.last
|
||||
if (last === undefined || last === null) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
if (state.lazyLoading) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
commit(MUTATION_TYPES.CHANGE_LAZY_LOADING, true)
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
try {
|
||||
const res: Response<Array<Status>> = await client.get<Array<Status>>(`/accounts/${info.account.id}/statuses`, { max_id: last.id, limit: 40 })
|
||||
commit(MUTATION_TYPES.INSERT_TIMELINE, res.data)
|
||||
} finally {
|
||||
commit(MUTATION_TYPES.CHANGE_LAZY_LOADING, false)
|
||||
}
|
||||
return null
|
||||
},
|
||||
clearTimeline: ({ commit }) => {
|
||||
commit(MUTATION_TYPES.UPDATE_TIMELINE, [])
|
||||
}
|
||||
}
|
||||
|
||||
const Timeline: Module<TimelineState, RootState> = {
|
||||
namespaced: true,
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
actions: actions
|
||||
}
|
||||
|
||||
export default Timeline
|
|
@ -1,111 +0,0 @@
|
|||
import Mastodon from 'megalodon'
|
||||
|
||||
const TootDetail = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
message: null,
|
||||
ancestors: [],
|
||||
descendants: []
|
||||
},
|
||||
mutations: {
|
||||
changeToot (state, message) {
|
||||
state.message = message
|
||||
},
|
||||
updateAncestors (state, ancestors) {
|
||||
state.ancestors = ancestors
|
||||
},
|
||||
updateDescendants (state, descendants) {
|
||||
state.descendants = descendants
|
||||
},
|
||||
updateAncestorsToot (state, message) {
|
||||
// Replace target message in ancestors
|
||||
state.ancestors = state.ancestors.map((toot) => {
|
||||
if (toot.id === message.id) {
|
||||
return message
|
||||
} else if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
// When user reblog/favourite a reblogged toot, target message is a original toot.
|
||||
// So, a message which is received now is original toot.
|
||||
const reblog = {
|
||||
reblog: message
|
||||
}
|
||||
return Object.assign(toot, reblog)
|
||||
} else {
|
||||
return toot
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteAncestorsToot (state, message) {
|
||||
state.ancestors = state.ancestors.filter((toot) => {
|
||||
if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
return false
|
||||
} else {
|
||||
return toot.id !== message.id
|
||||
}
|
||||
})
|
||||
},
|
||||
updateToot (state, message) {
|
||||
if (state.message.id === message.id) {
|
||||
state.message = message
|
||||
} else if (state.message.reblog !== null && state.message.reblog.id === message.id) {
|
||||
// When user reblog/favourite a reblogged toot, target message is a original toot.
|
||||
// So, a message which is received now is original toot.
|
||||
const reblog = {
|
||||
reblog: message
|
||||
}
|
||||
state.message = Object.assign({}, state.message, reblog)
|
||||
}
|
||||
},
|
||||
deleteToot (state, message) {
|
||||
if (state.message.id === message.id) {
|
||||
state.message = null
|
||||
}
|
||||
},
|
||||
updateDescendantsToot (state, message) {
|
||||
// Replace target message in descendants
|
||||
state.descendants = state.descendants.map((toot) => {
|
||||
if (toot.id === message.id) {
|
||||
return message
|
||||
} else if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
// When user reblog/favourite a reblogged toot, target message is a original toot.
|
||||
// So, a message which is received now is original toot.
|
||||
const reblog = {
|
||||
reblog: message
|
||||
}
|
||||
return Object.assign(toot, reblog)
|
||||
} else {
|
||||
return toot
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteDescendantsToot (state, message) {
|
||||
state.descendants = state.descendants.filter((toot) => {
|
||||
if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
return false
|
||||
} else {
|
||||
return toot.id !== message.id
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
changeToot ({ commit }, message) {
|
||||
commit('updateAncestors', [])
|
||||
commit('updateDescendants', [])
|
||||
commit('changeToot', message)
|
||||
},
|
||||
fetchToot ({ commit, rootState }, message) {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
return client.get(`/statuses/${message.id}/context`, { limit: 40 })
|
||||
.then(res => {
|
||||
commit('updateAncestors', res.data.ancestors)
|
||||
commit('updateDescendants', res.data.descendants)
|
||||
return res.data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TootDetail
|
|
@ -0,0 +1,142 @@
|
|||
import Mastodon, { Status, Context, Response } from 'megalodon'
|
||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
export interface TootDetailState {
|
||||
message: Status | null,
|
||||
ancestors: Array<Status>,
|
||||
descendants: Array<Status>
|
||||
}
|
||||
|
||||
const state = (): TootDetailState => ({
|
||||
message: null,
|
||||
ancestors: [],
|
||||
descendants: []
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
CHANGE_TOOT: 'changeToot',
|
||||
UPDATE_ANCESTORS: 'updateAncestors',
|
||||
UPDATE_DESCENDANTS: 'updateDescendants',
|
||||
UPDATE_ANCESTORS_TOOT: 'updateAncestorsToot',
|
||||
DELETE_ANCESTORS_TOOT: 'deleteAncestorsToot',
|
||||
UPDATE_TOOT: 'updateToot',
|
||||
DELETE_TOOT: 'deleteToot',
|
||||
UPDATE_DESCENDANTS_TOOT: 'updateDescendantsToot',
|
||||
DELETE_DESCENDANTS_TOOT: 'deleteDescendantsToot'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<TootDetailState> = {
|
||||
[MUTATION_TYPES.CHANGE_TOOT]: (state, message: Status) => {
|
||||
state.message = message
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_ANCESTORS]: (state, ancestors: Array<Status>) => {
|
||||
state.ancestors = ancestors
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_DESCENDANTS]: (state, descendants: Array<Status>) => {
|
||||
state.descendants = descendants
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_ANCESTORS_TOOT]: (state, message: Status) => {
|
||||
// Replace target message in ancestors
|
||||
state.ancestors = state.ancestors.map((toot) => {
|
||||
if (toot.id === message.id) {
|
||||
return message
|
||||
} else if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
// When user reblog/favourite a reblogged toot, target message is a original toot.
|
||||
// So, a message which is received now is original toot.
|
||||
const reblog = {
|
||||
reblog: message
|
||||
}
|
||||
return Object.assign(toot, reblog)
|
||||
} else {
|
||||
return toot
|
||||
}
|
||||
})
|
||||
},
|
||||
[MUTATION_TYPES.DELETE_ANCESTORS_TOOT]: (state, message: Status) => {
|
||||
state.ancestors = state.ancestors.filter((toot) => {
|
||||
if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
return false
|
||||
} else {
|
||||
return toot.id !== message.id
|
||||
}
|
||||
})
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_TOOT]: (state, message: Status) => {
|
||||
if (state.message === null) {
|
||||
return
|
||||
}
|
||||
if (state.message.id === message.id) {
|
||||
state.message = message
|
||||
} else if (state.message.reblog !== null && state.message.reblog.id === message.id) {
|
||||
// When user reblog/favourite a reblogged toot, target message is a original toot.
|
||||
// So, a message which is received now is original toot.
|
||||
const reblog = {
|
||||
reblog: message
|
||||
}
|
||||
state.message = Object.assign({}, state.message, reblog)
|
||||
}
|
||||
},
|
||||
[MUTATION_TYPES.DELETE_TOOT]: (state, message: Status) => {
|
||||
if (state.message === null) {
|
||||
return
|
||||
}
|
||||
if (state.message.id === message.id) {
|
||||
state.message = null
|
||||
}
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_DESCENDANTS_TOOT]: (state, message: Status) => {
|
||||
// Replace target message in descendants
|
||||
state.descendants = state.descendants.map((toot) => {
|
||||
if (toot.id === message.id) {
|
||||
return message
|
||||
} else if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
// When user reblog/favourite a reblogged toot, target message is a original toot.
|
||||
// So, a message which is received now is original toot.
|
||||
const reblog = {
|
||||
reblog: message
|
||||
}
|
||||
return Object.assign(toot, reblog)
|
||||
} else {
|
||||
return toot
|
||||
}
|
||||
})
|
||||
},
|
||||
[MUTATION_TYPES.DELETE_DESCENDANTS_TOOT]: (state, message: Status) => {
|
||||
state.descendants = state.descendants.filter((toot) => {
|
||||
if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
return false
|
||||
} else {
|
||||
return toot.id !== message.id
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const actions: ActionTree<TootDetailState, RootState> = {
|
||||
changeToot: ({ commit }, message: Status) => {
|
||||
commit(MUTATION_TYPES.UPDATE_ANCESTORS, [])
|
||||
commit(MUTATION_TYPES.UPDATE_DESCENDANTS, [])
|
||||
commit(MUTATION_TYPES.CHANGE_TOOT, message)
|
||||
},
|
||||
fetchToot: async ({ commit, rootState }, message: Status) => {
|
||||
const client = new Mastodon(
|
||||
rootState.TimelineSpace.account.accessToken!,
|
||||
rootState.TimelineSpace.account.baseURL + '/api/v1'
|
||||
)
|
||||
const res: Response<Context> = await client.get<Context>(`/statuses/${message.id}/context`, { limit: 40 })
|
||||
|
||||
commit(MUTATION_TYPES.UPDATE_ANCESTORS, res.data.ancestors)
|
||||
commit(MUTATION_TYPES.UPDATE_DESCENDANTS, res.data.descendants)
|
||||
return res.data
|
||||
}
|
||||
}
|
||||
|
||||
const TootDetail: Module<TootDetailState, RootState> = {
|
||||
namespaced: true,
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
actions: actions
|
||||
}
|
||||
|
||||
export default TootDetail
|
Loading…
Reference in New Issue