Merge pull request #3967 from h3poteto/iss-2500/account
refs #2500 Change account database to sqlite3
This commit is contained in:
commit
ddb76ca20e
|
@ -108,6 +108,7 @@
|
|||
"rc": "^1.2.7",
|
||||
"sanitize-html": "^2.8.1",
|
||||
"simplayer": "0.0.8",
|
||||
"sqlite3": "^5.1.4",
|
||||
"system-font-families": "^0.6.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"unicode-emoji-json": "^0.4.0",
|
||||
|
|
|
@ -3,7 +3,7 @@ import { createStore, Store } from 'vuex'
|
|||
import { ipcMain, ipcRenderer } from '~/spec/mock/electron'
|
||||
import GlobalHeader, { GlobalHeaderState } from '~/src/renderer/store/GlobalHeader'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
;(window as any as MyWindow).ipcRenderer = ipcRenderer
|
||||
;((window as any) as MyWindow).ipcRenderer = ipcRenderer
|
||||
|
||||
const state = (): GlobalHeaderState => {
|
||||
return {
|
||||
|
@ -58,21 +58,6 @@ describe('GlobalHeader', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('refreshAccounts', () => {
|
||||
beforeEach(() => {
|
||||
ipcMain.handle('refresh-accounts', () => {
|
||||
return ['accounts']
|
||||
})
|
||||
})
|
||||
afterEach(() => {
|
||||
ipcMain.removeHandler('refresh-accounts')
|
||||
})
|
||||
it('should be refreshed', async () => {
|
||||
await store.dispatch('GlobalHeader/refreshAccounts')
|
||||
expect(store.state.GlobalHeader.accounts).toEqual(['accounts'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('removeShortcutEvents', () => {
|
||||
it('should be removed', async () => {
|
||||
const removed = await store.dispatch('GlobalHeader/removeShortcutEvents')
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { createStore, Store } from 'vuex'
|
||||
import { ipcMain, ipcRenderer } from '~/spec/mock/electron'
|
||||
import { ipcRenderer } from '~/spec/mock/electron'
|
||||
import Login, { LoginState } from '@/store/Login'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { RootState } from '@/store'
|
||||
;(window as any as MyWindow).ipcRenderer = ipcRenderer
|
||||
;((window as any) as MyWindow).ipcRenderer = ipcRenderer
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
|
@ -13,8 +13,10 @@ jest.mock('megalodon', () => ({
|
|||
|
||||
const state = (): LoginState => {
|
||||
return {
|
||||
selectedInstance: null,
|
||||
domain: null,
|
||||
searching: false,
|
||||
server: null,
|
||||
appData: null,
|
||||
sns: 'mastodon'
|
||||
}
|
||||
}
|
||||
|
@ -47,34 +49,10 @@ describe('Login', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('fetchLogin', () => {
|
||||
describe('error', () => {
|
||||
it('should return error', async () => {
|
||||
ipcMain.handle('get-auth-url', () => {
|
||||
throw new Error()
|
||||
})
|
||||
await store.dispatch('Login/fetchLogin', 'pleroma.io').catch((err: Error) => {
|
||||
expect(err instanceof Error).toEqual(true)
|
||||
})
|
||||
ipcMain.removeHandler('get-auth-url')
|
||||
})
|
||||
})
|
||||
describe('success', () => {
|
||||
it('should return url', async () => {
|
||||
ipcMain.handle('get-auth-url', () => {
|
||||
return 'http://example.com/auth'
|
||||
})
|
||||
const url = await store.dispatch('Login/fetchLogin', 'pleroma.io')
|
||||
expect(url).toEqual('http://example.com/auth')
|
||||
ipcMain.removeHandler('get-auth-url')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('pageBack', () => {
|
||||
it('should reset instance', () => {
|
||||
store.dispatch('Login/pageBack')
|
||||
expect(store.state.Login.selectedInstance).toEqual(null)
|
||||
expect(store.state.Login.domain).toEqual(null)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -82,7 +60,7 @@ describe('Login', () => {
|
|||
it('should change instance', async () => {
|
||||
const result = await store.dispatch('Login/confirmInstance', 'pleroma.io')
|
||||
expect(result).toEqual(true)
|
||||
expect(store.state.Login.selectedInstance).toEqual('pleroma.io')
|
||||
expect(store.state.Login.domain).toEqual('pleroma.io')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,157 +0,0 @@
|
|||
import { createStore, Store } from 'vuex'
|
||||
import { ipcMain, ipcRenderer } from '~/spec/mock/electron'
|
||||
import Account, { AccountState } from '@/store/Preferences/Account'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { RootState } from '@/store'
|
||||
;(window as any as MyWindow).ipcRenderer = ipcRenderer
|
||||
|
||||
const account: LocalAccount = {
|
||||
_id: 'sample',
|
||||
baseURL: 'http://example.com',
|
||||
domain: 'example.com',
|
||||
clientId: 'hoge',
|
||||
clientSecret: 'hogehoge',
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
username: null,
|
||||
accountId: null,
|
||||
avatar: null,
|
||||
order: 1
|
||||
}
|
||||
|
||||
const state = (): AccountState => {
|
||||
return {
|
||||
accounts: [],
|
||||
accountLoading: false
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: Account.actions,
|
||||
mutations: Account.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const preferencesStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Account: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
describe('Account', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
Preferences: preferencesStore()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('loadAccounts', () => {
|
||||
it('error', async () => {
|
||||
ipcMain.handle('list-accounts', async () => {
|
||||
throw new Error()
|
||||
})
|
||||
|
||||
await store.dispatch('Preferences/Account/loadAccounts').catch((err: Error) => {
|
||||
expect(err instanceof Error).toEqual(true)
|
||||
})
|
||||
ipcMain.removeHandler('list-accounts')
|
||||
})
|
||||
it('success', async () => {
|
||||
ipcMain.handle('list-accounts', () => {
|
||||
return [account]
|
||||
})
|
||||
await store.dispatch('Preferences/Account/loadAccounts')
|
||||
expect(store.state.Preferences.Account.accounts).toEqual([account])
|
||||
ipcMain.removeHandler('list-accounts')
|
||||
})
|
||||
})
|
||||
|
||||
describe('removeAccount', () => {
|
||||
it('error', async () => {
|
||||
ipcMain.handle('remove-account', async () => {
|
||||
throw new Error()
|
||||
})
|
||||
await store.dispatch('Preferences/Account/removeAccount', account).catch((err: Error) => {
|
||||
expect(err instanceof Error).toEqual(true)
|
||||
})
|
||||
ipcMain.removeHandler('remove-account')
|
||||
})
|
||||
it('success', async () => {
|
||||
ipcMain.handle('remove-account', () => {
|
||||
return true
|
||||
})
|
||||
const res = await store.dispatch('Preferences/Account/removeAccount', account)
|
||||
expect(res).toEqual(undefined)
|
||||
ipcMain.removeHandler('remove-account')
|
||||
})
|
||||
})
|
||||
|
||||
describe('forwardAccount', () => {
|
||||
it('error', async () => {
|
||||
ipcMain.handle('forward-account', async () => {
|
||||
throw new Error()
|
||||
})
|
||||
await store.dispatch('Preferences/Account/forwardAccount', account).catch((err: Error) => {
|
||||
expect(err instanceof Error).toEqual(true)
|
||||
})
|
||||
ipcMain.removeHandler('forward-account')
|
||||
})
|
||||
it('success', async () => {
|
||||
ipcMain.handle('forward-account', () => {
|
||||
return {}
|
||||
})
|
||||
const res = await store.dispatch('Preferences/Account/forwardAccount', account)
|
||||
expect(res).toEqual(undefined)
|
||||
ipcMain.removeHandler('forward-account')
|
||||
})
|
||||
})
|
||||
|
||||
describe('backwardAccount', () => {
|
||||
it('error', async () => {
|
||||
ipcMain.handle('backward-account', () => {
|
||||
throw new Error()
|
||||
})
|
||||
await store.dispatch('Preferences/Account/backwardAccount', account).catch((err: Error) => {
|
||||
expect(err instanceof Error).toEqual(true)
|
||||
})
|
||||
ipcMain.removeHandler('backward-account')
|
||||
})
|
||||
it('success', async () => {
|
||||
ipcMain.handle('backward-account', () => {
|
||||
return {}
|
||||
})
|
||||
const res = await store.dispatch('Preferences/Account/backwardAccount', account)
|
||||
expect(res).toEqual(undefined)
|
||||
ipcMain.removeHandler('backward-account')
|
||||
})
|
||||
})
|
||||
|
||||
describe('removeAllAccounts', () => {
|
||||
it('error', async () => {
|
||||
ipcMain.handle('remove-all-accounts', () => {
|
||||
throw new Error()
|
||||
})
|
||||
await store.dispatch('Preferences/Account/removeAllAccounts', account).catch((err: Error) => {
|
||||
expect(err instanceof Error).toEqual(true)
|
||||
})
|
||||
ipcMain.removeHandler('remove-all-accounts')
|
||||
})
|
||||
it('success', async () => {
|
||||
ipcMain.handle('remove-all-accounts', () => {
|
||||
return {}
|
||||
})
|
||||
const res = await store.dispatch('Preferences/Account/removeAllAccounts', account)
|
||||
expect(res).toEqual(undefined)
|
||||
ipcMain.removeHandler('remove-all-accounts')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,286 +0,0 @@
|
|||
import { RootState } from '@/store'
|
||||
import { Entity, Response } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import { ipcMain, ipcRenderer } from '~/spec/mock/electron'
|
||||
import TimelineSpace, { TimelineSpaceState, blankAccount } from '~/src/renderer/store/TimelineSpace'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
;((window as any) as MyWindow).ipcRenderer = ipcRenderer
|
||||
|
||||
const emacsEmoji: Entity.Emoji = {
|
||||
shortcode: 'emacs',
|
||||
url: 'http://example.com/emacs',
|
||||
static_url: 'http://example.com/emacs',
|
||||
visible_in_picker: true
|
||||
}
|
||||
const rubyEmoji: Entity.Emoji = {
|
||||
shortcode: 'ruby',
|
||||
url: 'http://example.com/ruby',
|
||||
static_url: 'http://example.com/ruby',
|
||||
visible_in_picker: true
|
||||
}
|
||||
|
||||
const mockedInstance: Entity.Instance = {
|
||||
uri: 'http://pleroma.io',
|
||||
title: 'pleroma',
|
||||
description: '',
|
||||
email: 'test@example.com',
|
||||
version: '2.5.0 (compatible; Pleroma 0.9.0-3363-g7c5d2dc7)',
|
||||
thumbnail: null,
|
||||
urls: {
|
||||
streaming_api: 'wss://pleroma.io'
|
||||
},
|
||||
stats: {
|
||||
user_count: 10,
|
||||
status_count: 1000,
|
||||
domain_count: 100
|
||||
},
|
||||
languages: ['en'],
|
||||
contact_account: null,
|
||||
max_toot_chars: 5000
|
||||
}
|
||||
|
||||
const mockClient = {
|
||||
getInstance: () => {
|
||||
return new Promise<Response<Entity.Instance>>(resolve => {
|
||||
const res: Response<Entity.Instance> = {
|
||||
data: mockedInstance,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
},
|
||||
getInstanceCustomEmojis: () => {
|
||||
return new Promise<Response<Array<Entity.Emoji>>>(resolve => {
|
||||
const res: Response<Array<Entity.Emoji>> = {
|
||||
data: [emacsEmoji, rubyEmoji],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
detector: jest.fn(() => 'pleroma'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const state = (): TimelineSpaceState => {
|
||||
return {
|
||||
account: blankAccount,
|
||||
bindingAccount: null,
|
||||
loading: false,
|
||||
emojis: [],
|
||||
tootMax: 500,
|
||||
timelineSetting: {
|
||||
unreadNotification: {
|
||||
direct: true,
|
||||
local: true,
|
||||
public: true
|
||||
},
|
||||
useMarker: {
|
||||
home: false,
|
||||
notifications: true
|
||||
}
|
||||
},
|
||||
sns: 'mastodon',
|
||||
filters: []
|
||||
}
|
||||
}
|
||||
|
||||
const homeStore = {
|
||||
namespaced: true,
|
||||
actions: {
|
||||
fetchTimeline: jest.fn()
|
||||
}
|
||||
}
|
||||
|
||||
const notificationStore = {
|
||||
namespaced: true,
|
||||
actions: {
|
||||
fetchNotifications: jest.fn()
|
||||
}
|
||||
}
|
||||
|
||||
const DMStore = {
|
||||
namespaced: true,
|
||||
actions: {
|
||||
fetchTimeline: jest.fn()
|
||||
}
|
||||
}
|
||||
|
||||
const LocalStore = {
|
||||
namespaced: true,
|
||||
actions: {
|
||||
fetchLocalTimeline: jest.fn()
|
||||
}
|
||||
}
|
||||
|
||||
const PublicStore = {
|
||||
namespaced: true,
|
||||
actions: {
|
||||
fetchPublicTimeline: jest.fn()
|
||||
}
|
||||
}
|
||||
|
||||
const MentionStore = {
|
||||
namespaced: true,
|
||||
actions: {
|
||||
fetchMentions: jest.fn()
|
||||
}
|
||||
}
|
||||
|
||||
const contentsStore = {
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Home: homeStore,
|
||||
Notifications: notificationStore,
|
||||
DirectMessages: DMStore,
|
||||
Local: LocalStore,
|
||||
Public: PublicStore,
|
||||
Mentions: MentionStore
|
||||
},
|
||||
actions: {
|
||||
changeLoading: jest.fn()
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Contents: contentsStore
|
||||
},
|
||||
state: state(),
|
||||
actions: TimelineSpace.actions,
|
||||
mutations: TimelineSpace.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('TimelineSpace', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: initStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('localAccount', () => {
|
||||
describe('account already exists', () => {
|
||||
beforeEach(() => {
|
||||
ipcMain.handle('get-local-account', () => {
|
||||
return {
|
||||
username: 'test'
|
||||
}
|
||||
})
|
||||
})
|
||||
afterEach(() => {
|
||||
ipcMain.removeHandler('get-local-account')
|
||||
})
|
||||
it('should be updated', async () => {
|
||||
await store.dispatch('TimelineSpace/localAccount', 1)
|
||||
expect(store.state.TimelineSpace.account.username).toEqual('test')
|
||||
})
|
||||
})
|
||||
|
||||
describe('account does not exist', () => {
|
||||
beforeEach(() => {
|
||||
ipcMain.handle('get-local-account', () => {
|
||||
return {}
|
||||
})
|
||||
ipcMain.handle('update-account', () => {
|
||||
return {
|
||||
username: 'fetched'
|
||||
}
|
||||
})
|
||||
})
|
||||
afterEach(() => {
|
||||
ipcMain.removeHandler('get-local-account')
|
||||
ipcMain.removeHandler('update-account')
|
||||
})
|
||||
it('should be fetched', async () => {
|
||||
await store.dispatch('TimelineSpace/localAccount', 1)
|
||||
expect(store.state.TimelineSpace.account.username).toEqual('fetched')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('detectSNS', () => {
|
||||
describe('API is pleroma', () => {
|
||||
it('should be detected', async () => {
|
||||
await store.dispatch('TimelineSpace/detectSNS')
|
||||
expect(store.state.TimelineSpace.sns).toEqual('pleroma')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchEmojis', () => {
|
||||
it('should be updated', async () => {
|
||||
await store.dispatch('TimelineSpace/fetchEmojis', {})
|
||||
expect(store.state.TimelineSpace.emojis).toEqual([emacsEmoji, rubyEmoji])
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchInstance', () => {
|
||||
it('should be updated', async () => {
|
||||
await store.dispatch('TimelineSpace/fetchInstance', {})
|
||||
expect(store.state.TimelineSpace.tootMax).toEqual(mockedInstance.max_toot_chars)
|
||||
})
|
||||
})
|
||||
|
||||
describe('loadUnreadNotification', () => {
|
||||
describe('success', () => {
|
||||
it('should be updated', async () => {
|
||||
ipcMain.handle('get-account-setting', () => {
|
||||
return {
|
||||
accountID: 'sample',
|
||||
timeline: {
|
||||
unreadNotification: {
|
||||
direct: false,
|
||||
local: false,
|
||||
public: false
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
await store.dispatch('TimelineSpace/loadTimelineSetting')
|
||||
expect(store.state.TimelineSpace.timelineSetting).toEqual({
|
||||
unreadNotification: {
|
||||
direct: false,
|
||||
local: false,
|
||||
public: false
|
||||
}
|
||||
})
|
||||
ipcMain.removeHandler('get-account-setting')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchContentsTimelines', () => {
|
||||
it('should be called', async () => {
|
||||
await store.dispatch('TimelineSpace/fetchContentsTimelines', {})
|
||||
expect(homeStore.actions.fetchTimeline).toHaveBeenCalled()
|
||||
expect(notificationStore.actions.fetchNotifications).toHaveBeenCalled()
|
||||
expect(DMStore.actions.fetchTimeline).toHaveBeenCalled()
|
||||
expect(LocalStore.actions.fetchLocalTimeline).toHaveBeenCalled()
|
||||
expect(PublicStore.actions.fetchPublicTimeline).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,228 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import DirectMessages, { DirectMessagesState } from '@/store/TimelineSpace/Contents/DirectMessages'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const mockClient = {
|
||||
getConversationTimeline: () => {
|
||||
return new Promise<Response<Array<Entity.Conversation>>>(resolve => {
|
||||
const res: Response<Array<Entity.Conversation>> = {
|
||||
data: [conversation1],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const conversation1: Entity.Conversation = {
|
||||
id: '1',
|
||||
accounts: [account],
|
||||
last_status: status1,
|
||||
unread: false
|
||||
}
|
||||
|
||||
const conversation2: Entity.Conversation = {
|
||||
id: '2',
|
||||
accounts: [account],
|
||||
last_status: status2,
|
||||
unread: false
|
||||
}
|
||||
|
||||
let state = (): DirectMessagesState => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: DirectMessages.actions,
|
||||
mutations: DirectMessages.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
DirectMessages: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
},
|
||||
sns: 'mastodon'
|
||||
},
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Home', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchTimeline', () => {
|
||||
it('should be updated', async () => {
|
||||
const statuses = await store.dispatch('TimelineSpace/Contents/DirectMessages/fetchTimeline')
|
||||
expect(statuses).toEqual([status1])
|
||||
expect(store.state.TimelineSpace.Contents.DirectMessages.timeline).toEqual([status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('lazyFetchTimeline', () => {
|
||||
describe('success', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1]
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should be updated', async () => {
|
||||
mockClient.getConversationTimeline = () => {
|
||||
return new Promise<Response<Array<Entity.Conversation>>>(resolve => {
|
||||
const res: Response<Array<Entity.Conversation>> = {
|
||||
data: [conversation2],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
await store.dispatch('TimelineSpace/Contents/DirectMessages/lazyFetchTimeline', status1)
|
||||
expect(store.state.TimelineSpace.Contents.DirectMessages.lazyLoading).toEqual(false)
|
||||
expect(store.state.TimelineSpace.Contents.DirectMessages.timeline).toEqual([status1, status2])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,321 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import Favourites, { FavouritesState } from '@/store/TimelineSpace/Contents/Favourites'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const mockClient = {
|
||||
getFavourites: () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status1],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const localAccount: LocalAccount = {
|
||||
_id: '1',
|
||||
baseURL: 'http://localhost',
|
||||
domain: 'localhost',
|
||||
clientId: 'id',
|
||||
clientSecret: 'secret',
|
||||
accessToken: 'token',
|
||||
refreshToken: null,
|
||||
username: 'hoge',
|
||||
accountId: '1',
|
||||
avatar: null,
|
||||
order: 1
|
||||
}
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
let state = (): FavouritesState => {
|
||||
return {
|
||||
favourites: [],
|
||||
lazyLoading: false,
|
||||
maxId: null
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: Favourites.actions,
|
||||
mutations: Favourites.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Favourites: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
},
|
||||
sns: 'mastodon'
|
||||
},
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Favourites', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchFavourites', () => {
|
||||
it('does not exist header', async () => {
|
||||
await store.dispatch('TimelineSpace/Contents/Favourites/fetchFavourites', localAccount)
|
||||
expect(store.state.TimelineSpace.Contents.Favourites.favourites).toEqual([status1])
|
||||
expect(store.state.TimelineSpace.Contents.Favourites.maxId).toEqual(null)
|
||||
})
|
||||
|
||||
it('link is null', async () => {
|
||||
mockClient.getFavourites = () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status1],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {
|
||||
link: null
|
||||
}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
await store.dispatch('TimelineSpace/Contents/Favourites/fetchFavourites', localAccount)
|
||||
expect(store.state.TimelineSpace.Contents.Favourites.favourites).toEqual([status1])
|
||||
expect(store.state.TimelineSpace.Contents.Favourites.maxId).toEqual(null)
|
||||
})
|
||||
|
||||
it('link exists in header', async () => {
|
||||
mockClient.getFavourites = () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status1],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {
|
||||
link: '<http://localhost?max_id=2>; rel="next"'
|
||||
}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
|
||||
await store.dispatch('TimelineSpace/Contents/Favourites/fetchFavourites', localAccount)
|
||||
expect(store.state.TimelineSpace.Contents.Favourites.favourites).toEqual([status1])
|
||||
expect(store.state.TimelineSpace.Contents.Favourites.maxId).toEqual('2')
|
||||
})
|
||||
})
|
||||
|
||||
describe('lazyFetchFavourites', () => {
|
||||
describe('lazyLoading', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
favourites: [],
|
||||
lazyLoading: true,
|
||||
maxId: null
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should not be updated', async () => {
|
||||
const res = await store.dispatch('TimelineSpace/Contents/Favourites/lazyFetchFavourites')
|
||||
expect(res).toEqual(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('does not exist maxId', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
favourites: [],
|
||||
lazyLoading: false,
|
||||
maxId: null
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should not be updated', async () => {
|
||||
const res = await store.dispatch('TimelineSpace/Contents/Favourites/lazyFetchFavourites')
|
||||
expect(res).toEqual(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetch', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
favourites: [status1],
|
||||
lazyLoading: false,
|
||||
maxId: '2'
|
||||
}
|
||||
}
|
||||
})
|
||||
it('link is null', async () => {
|
||||
mockClient.getFavourites = () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status2],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {
|
||||
link: null
|
||||
}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
|
||||
await store.dispatch('TimelineSpace/Contents/Favourites/lazyFetchFavourites')
|
||||
expect(store.state.TimelineSpace.Contents.Favourites.favourites).toEqual([status1, status2])
|
||||
expect(store.state.TimelineSpace.Contents.Favourites.maxId).toEqual(null)
|
||||
})
|
||||
|
||||
it('link exists in header', async () => {
|
||||
mockClient.getFavourites = () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status2],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {
|
||||
link: '<http://localhost?max_id=3>; rel="next"'
|
||||
}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
|
||||
await store.dispatch('TimelineSpace/Contents/Favourites/lazyFetchFavourites')
|
||||
expect(store.state.TimelineSpace.Contents.Favourites.favourites).toEqual([status1, status2])
|
||||
expect(store.state.TimelineSpace.Contents.Favourites.maxId).toEqual('3')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,221 +0,0 @@
|
|||
import { Entity, Response } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import FollowRequests, { FollowRequestsState } from '@/store/TimelineSpace/Contents/FollowRequests'
|
||||
import { SideMenuState } from '@/store/TimelineSpace/SideMenu'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const mockClient = {
|
||||
getFollowRequests: () => {
|
||||
return new Promise<Response<Array<Entity.Account>>>(resolve => {
|
||||
const res: Response<Array<Entity.Account>> = {
|
||||
data: [account],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
},
|
||||
acceptFollowRequest: () => {
|
||||
return new Promise<Response<{}>>(resolve => {
|
||||
const res: Response<{}> = {
|
||||
data: {},
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
},
|
||||
rejectFollowRequest: () => {
|
||||
return new Promise<Response<{}>>(resolve => {
|
||||
const res: Response<{}> = {
|
||||
data: {},
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
let state = (): FollowRequestsState => {
|
||||
return {
|
||||
requests: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: FollowRequests.actions,
|
||||
mutations: FollowRequests.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const sideMenuState = (): SideMenuState => {
|
||||
return {
|
||||
unreadHomeTimeline: false,
|
||||
unreadNotifications: false,
|
||||
unreadMentions: false,
|
||||
unreadLocalTimeline: false,
|
||||
unreadDirectMessagesTimeline: false,
|
||||
unreadPublicTimeline: false,
|
||||
unreadFollowRequests: false,
|
||||
lists: [],
|
||||
tags: [],
|
||||
collapse: false,
|
||||
enabledTimelines: {
|
||||
home: true,
|
||||
notification: true,
|
||||
mention: true,
|
||||
direct: true,
|
||||
favourite: true,
|
||||
bookmark: true,
|
||||
local: true,
|
||||
public: true,
|
||||
tag: true,
|
||||
list: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sideMenuStore = () => ({
|
||||
namespaced: true,
|
||||
state: sideMenuState(),
|
||||
actions: {
|
||||
fetchFollowRequests: jest.fn()
|
||||
},
|
||||
mutations: {}
|
||||
})
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
FollowRequests: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
SideMenu: sideMenuStore(),
|
||||
Contents: contentsStore()
|
||||
},
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
},
|
||||
sns: 'mastodon'
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Home', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchRequests', () => {
|
||||
it('should be updated', async () => {
|
||||
await store.dispatch('TimelineSpace/Contents/FollowRequests/fetchRequests')
|
||||
expect(store.state.TimelineSpace.Contents.FollowRequests.requests).toEqual([account])
|
||||
})
|
||||
})
|
||||
|
||||
describe('acceptRequest', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
requests: [account]
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should be succeed', async () => {
|
||||
mockClient.getFollowRequests = () => {
|
||||
return new Promise<Response<Array<Entity.Account>>>(resolve => {
|
||||
const res: Response<Array<Entity.Account>> = {
|
||||
data: [],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
|
||||
await store.dispatch('TimelineSpace/Contents/FollowRequests/acceptRequest', account)
|
||||
expect(store.state.TimelineSpace.Contents.FollowRequests.requests).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('rejectRequest', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
requests: [account]
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should be succeed', async () => {
|
||||
mockClient.getFollowRequests = () => {
|
||||
return new Promise<Response<Array<Entity.Account>>>(resolve => {
|
||||
const res: Response<Array<Entity.Account>> = {
|
||||
data: [],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
|
||||
await store.dispatch('TimelineSpace/Contents/FollowRequests/rejectRequest', account)
|
||||
expect(store.state.TimelineSpace.Contents.FollowRequests.requests).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,96 +0,0 @@
|
|||
import { IpcMainInvokeEvent } from 'electron'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import { ipcMain, ipcRenderer } from '~/spec/mock/electron'
|
||||
import { LocalTag } from '~/src/types/localTag'
|
||||
import List, { ListState } from '@/store/TimelineSpace/Contents/Hashtag/List'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { RootState } from '@/store'
|
||||
;(window as any as MyWindow).ipcRenderer = ipcRenderer
|
||||
|
||||
const tag1: LocalTag = {
|
||||
tagName: 'tag1',
|
||||
_id: '1'
|
||||
}
|
||||
|
||||
const tag2: LocalTag = {
|
||||
tagName: 'tag2',
|
||||
_id: '2'
|
||||
}
|
||||
|
||||
const state = (): ListState => {
|
||||
return {
|
||||
tags: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: List.actions,
|
||||
mutations: List.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const hashtagStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
List: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Hashtag: hashtagStore()
|
||||
}
|
||||
})
|
||||
|
||||
const sideMenuStore = {
|
||||
namespaced: true,
|
||||
actions: {
|
||||
listTags: jest.fn()
|
||||
}
|
||||
}
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
SideMenu: sideMenuStore,
|
||||
Contents: contentsStore()
|
||||
}
|
||||
})
|
||||
|
||||
describe('Hashtag/List', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('listTags', () => {
|
||||
it('should be updated', async () => {
|
||||
ipcMain.handle('list-hashtags', () => {
|
||||
return [tag1, tag2]
|
||||
})
|
||||
afterEach(() => {
|
||||
ipcMain.removeHandler('list-hashtags')
|
||||
})
|
||||
await store.dispatch('TimelineSpace/Contents/Hashtag/List/listTags')
|
||||
expect(store.state.TimelineSpace.Contents.Hashtag.List.tags).toEqual([tag1, tag2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('removeTag', () => {
|
||||
it('should be updated', async () => {
|
||||
ipcMain.handle('remove-hashtag', (_: IpcMainInvokeEvent, tag: LocalTag) => {
|
||||
expect(tag).toEqual(tag1)
|
||||
})
|
||||
await store.dispatch('TimelineSpace/Contents/Hashtag/List/removeTag', tag1)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,225 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import Tag, { TagState } from '@/store/TimelineSpace/Contents/Hashtag/Tag'
|
||||
import { LoadPositionWithTag } from '@/types/loadPosition'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const mockClient = {
|
||||
getTagTimeline: () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status1],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
let state = (): TagState => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [],
|
||||
unreads: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: Tag.actions,
|
||||
mutations: Tag.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const hashtagStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Tag: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Hashtag: hashtagStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Home', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetch', () => {
|
||||
it('should be updated', async () => {
|
||||
const statuses = await store.dispatch('TimelineSpace/Contents/Hashtag/Tag/fetch', 'tag')
|
||||
expect(statuses).toEqual([status1])
|
||||
expect(store.state.TimelineSpace.Contents.Hashtag.Tag.timeline).toEqual([status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('lazyFetchTimeline', () => {
|
||||
describe('success', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1],
|
||||
unreads: []
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should be updated', async () => {
|
||||
mockClient.getTagTimeline = () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status2],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
const loadPositionWithTag: LoadPositionWithTag = {
|
||||
status: status1,
|
||||
tag: 'tag'
|
||||
}
|
||||
await store.dispatch('TimelineSpace/Contents/Hashtag/Tag/lazyFetchTimeline', loadPositionWithTag)
|
||||
expect(store.state.TimelineSpace.Contents.Hashtag.Tag.lazyLoading).toEqual(false)
|
||||
expect(store.state.TimelineSpace.Contents.Hashtag.Tag.timeline).toEqual([status1, status2])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,224 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import Home, { HomeState } from '@/store/TimelineSpace/Contents/Home'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const mockClient = {
|
||||
getHomeTimeline: () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status1],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
let state = (): HomeState => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [],
|
||||
unreads: [],
|
||||
showReblogs: true,
|
||||
showReplies: true
|
||||
}
|
||||
}
|
||||
|
||||
const homeStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: Home.actions,
|
||||
mutations: Home.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Home: homeStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
},
|
||||
timelineSetting: {
|
||||
useMarker: {
|
||||
home: false,
|
||||
notifications: false
|
||||
}
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Home', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchTimeline', () => {
|
||||
it('should be updated', async () => {
|
||||
const statuses = await store.dispatch('TimelineSpace/Contents/Home/fetchTimeline')
|
||||
expect(statuses).toEqual([status1])
|
||||
expect(store.state.TimelineSpace.Contents.Home.timeline).toEqual([status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('lazyFetchTimeline', () => {
|
||||
describe('success', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1],
|
||||
unreads: [],
|
||||
showReblogs: true,
|
||||
showReplies: true
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should be updated', async () => {
|
||||
mockClient.getHomeTimeline = () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status2],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
|
||||
await store.dispatch('TimelineSpace/Contents/Home/lazyFetchTimeline', status1)
|
||||
expect(store.state.TimelineSpace.Contents.Home.lazyLoading).toEqual(false)
|
||||
expect(store.state.TimelineSpace.Contents.Home.timeline).toEqual([status1, status2])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,139 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import Edit, { EditState } from '@/store/TimelineSpace/Contents/Lists/Edit'
|
||||
import { RemoveAccountFromList } from '@/types/removeAccountFromList'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const mockClient = {
|
||||
getAccountsInList: () => {
|
||||
return new Promise<Response<Entity.Account[]>>(resolve => {
|
||||
const res: Response<Entity.Account[]> = {
|
||||
data: [account],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
},
|
||||
deleteAccountsFromList: () => {
|
||||
return new Promise<Response<{}>>(resolve => {
|
||||
const res: Response<{}> = {
|
||||
data: {},
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
const state = (): EditState => {
|
||||
return {
|
||||
members: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: Edit.actions,
|
||||
mutations: Edit.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const listsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Edit: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Lists: listsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
},
|
||||
sns: 'mastodon'
|
||||
},
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Lists/Edit', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchMembers', () => {
|
||||
it('should get', async () => {
|
||||
await store.dispatch('TimelineSpace/Contents/Lists/Edit/fetchMembers', 'id')
|
||||
expect(store.state.TimelineSpace.Contents.Lists.Edit.members).toEqual([account])
|
||||
})
|
||||
})
|
||||
|
||||
describe('removeAccount', () => {
|
||||
it('should be removed', async () => {
|
||||
const removeFromList: RemoveAccountFromList = {
|
||||
account: account,
|
||||
listId: 'id'
|
||||
}
|
||||
const res = await store.dispatch('TimelineSpace/Contents/Lists/Edit/removeAccount', removeFromList)
|
||||
expect(res.data).toEqual({})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,117 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import Index, { IndexState } from '@/store/TimelineSpace/Contents/Lists/Index'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const mockClient = {
|
||||
getLists: () => {
|
||||
return new Promise<Response<Array<Entity.List>>>(resolve => {
|
||||
const res: Response<Array<Entity.List>> = {
|
||||
data: [list],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
},
|
||||
createList: () => {
|
||||
return new Promise<Response<Entity.List>>(resolve => {
|
||||
const res: Response<Entity.List> = {
|
||||
data: list,
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const list: Entity.List = {
|
||||
id: '1',
|
||||
title: 'list1'
|
||||
}
|
||||
|
||||
const state = (): IndexState => {
|
||||
return {
|
||||
lists: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: Index.actions,
|
||||
mutations: Index.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const listsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Index: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Lists: listsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
},
|
||||
sns: 'mastodon'
|
||||
},
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Lists/Index', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchLists', () => {
|
||||
it('should be updated', async () => {
|
||||
await store.dispatch('TimelineSpace/Contents/Lists/Index/fetchLists')
|
||||
expect(store.state.TimelineSpace.Contents.Lists.Index.lists).toEqual([list])
|
||||
})
|
||||
})
|
||||
|
||||
describe('createList', () => {
|
||||
it('should be created', async () => {
|
||||
const res: Entity.List = await store.dispatch('TimelineSpace/Contents/Lists/Index/createList', 'list1')
|
||||
expect(res.title).toEqual('list1')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,225 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import Show, { ShowState } from '@/store/TimelineSpace/Contents/Lists/Show'
|
||||
import { LoadPositionWithList } from '@/types/loadPosition'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const mockClient = {
|
||||
getListTimeline: () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status1],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
let state = (): ShowState => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [],
|
||||
unreads: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: Show.actions,
|
||||
mutations: Show.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const listsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Show: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Lists: listsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Lists/Show', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchTimeline', () => {
|
||||
it('should be updated', async () => {
|
||||
await store.dispatch('TimelineSpace/Contents/Lists/Show/fetchTimeline', '1')
|
||||
expect(store.state.TimelineSpace.Contents.Lists.Show.timeline).toEqual([status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('lazyFetchTimeline', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1],
|
||||
unreads: []
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should be updated', async () => {
|
||||
mockClient.getListTimeline = () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status2],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
|
||||
const loadPosition: LoadPositionWithList = {
|
||||
status: status1,
|
||||
list_id: '1'
|
||||
}
|
||||
await store.dispatch('TimelineSpace/Contents/Lists/Show/lazyFetchTimeline', loadPosition)
|
||||
expect(store.state.TimelineSpace.Contents.Lists.Show.timeline).toEqual([status1, status2])
|
||||
expect(store.state.TimelineSpace.Contents.Lists.Show.lazyLoading).toEqual(false)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,214 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import Local, { LocalState } from '@/store/TimelineSpace/Contents/Local'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const mockClient = {
|
||||
getLocalTimeline: () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status1],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
let state = (): LocalState => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [],
|
||||
unreads: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: Local.actions,
|
||||
mutations: Local.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Local: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Home', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchLocalTimeline', () => {
|
||||
it('should be updated', async () => {
|
||||
const statuses = await store.dispatch('TimelineSpace/Contents/Local/fetchLocalTimeline')
|
||||
expect(statuses).toEqual([status1])
|
||||
expect(store.state.TimelineSpace.Contents.Local.timeline).toEqual([status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('lazyFetchTimeline', () => {
|
||||
describe('success', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1],
|
||||
unreads: []
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should be updated', async () => {
|
||||
mockClient.getLocalTimeline = () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status2],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
|
||||
await store.dispatch('TimelineSpace/Contents/Local/lazyFetchTimeline', status1)
|
||||
expect(store.state.TimelineSpace.Contents.Local.lazyLoading).toEqual(false)
|
||||
expect(store.state.TimelineSpace.Contents.Local.timeline).toEqual([status1, status2])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,248 +0,0 @@
|
|||
import { RootState } from '@/store'
|
||||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import Mentions from '~/src/renderer/store/TimelineSpace/Contents/Mentions'
|
||||
|
||||
const mockClient = {
|
||||
getNotifications: () => {
|
||||
return new Promise<Response<Entity.Notification[]>>(resolve => {
|
||||
const res: Response<Entity.Notification[]> = {
|
||||
data: [mention, reblog, favourite, follow],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
const status: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const mention: Entity.Notification = {
|
||||
account: account,
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
id: '1',
|
||||
status: status,
|
||||
type: 'mention'
|
||||
}
|
||||
|
||||
const reblog: Entity.Notification = {
|
||||
account: account,
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
id: '2',
|
||||
status: status,
|
||||
type: 'reblog'
|
||||
}
|
||||
|
||||
const favourite: Entity.Notification = {
|
||||
account: account,
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
id: '3',
|
||||
status: status,
|
||||
type: 'favourite'
|
||||
}
|
||||
|
||||
const follow: Entity.Notification = {
|
||||
account: account,
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
id: '4',
|
||||
type: 'follow'
|
||||
}
|
||||
|
||||
let state: Function = () => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
mentions: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: Mentions.actions,
|
||||
mutations: Mentions.mutations,
|
||||
getters: Mentions.getters
|
||||
}
|
||||
}
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Mentions: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
},
|
||||
timelineSetting: {
|
||||
useMarker: {
|
||||
home: false,
|
||||
notifications: false,
|
||||
mentions: false
|
||||
}
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Mentions', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchMentions', () => {
|
||||
it('should be updated', async () => {
|
||||
await store.dispatch('TimelineSpace/Contents/Mentions/fetchMentions')
|
||||
expect(store.state.TimelineSpace.Contents.Mentions.mentions).toEqual([mention, reblog, favourite, follow])
|
||||
})
|
||||
})
|
||||
|
||||
describe('lazyFetchMentions', () => {
|
||||
describe('loading', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
lazyLoading: true,
|
||||
heading: true,
|
||||
mentions: []
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should not be updated', async () => {
|
||||
const result = await store.dispatch('TimelineSpace/Contents/Mentions/lazyFetchMentions', {})
|
||||
expect(result).toEqual(null)
|
||||
})
|
||||
})
|
||||
|
||||
describe('success', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
mentions: [mention, reblog]
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should be updated', async () => {
|
||||
mockClient.getNotifications = () => {
|
||||
return new Promise<Response<Entity.Notification[]>>(resolve => {
|
||||
const res: Response<Entity.Notification[]> = {
|
||||
data: [favourite, follow],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
|
||||
await store.dispatch('TimelineSpace/Contents/Mentions/lazyFetchMentions', { id: 1 })
|
||||
expect(store.state.TimelineSpace.Contents.Mentions.mentions).toEqual([mention, reblog, favourite, follow])
|
||||
expect(store.state.TimelineSpace.Contents.Mentions.lazyLoading).toEqual(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('mentions', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
mentions: [mention, favourite, reblog, follow]
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should return only mentions', () => {
|
||||
const mentions = store.getters['TimelineSpace/Contents/Mentions/mentions']
|
||||
expect(mentions).toEqual([mention])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,293 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import Notifications, { NotificationsState } from '@/store/TimelineSpace/Contents/Notifications'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const mockClient = {
|
||||
getNotifications: () => {
|
||||
return new Promise<Response<Array<Entity.Notification>>>(resolve => {
|
||||
const res: Response<Array<Entity.Notification>> = {
|
||||
data: [notification1],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const account1: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
const account2: Entity.Account = {
|
||||
id: '2',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@mstdn.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://mstdn.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account1,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account1,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const rebloggedStatus: Entity.Status = {
|
||||
id: '3',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account1,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: status2,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const notification1: Entity.Notification = {
|
||||
id: '1',
|
||||
account: account2,
|
||||
status: status1,
|
||||
type: 'favourite',
|
||||
created_at: '2019-04-01T17:01:32'
|
||||
}
|
||||
|
||||
const notification2: Entity.Notification = {
|
||||
id: '2',
|
||||
account: account2,
|
||||
status: rebloggedStatus,
|
||||
type: 'mention',
|
||||
created_at: '2019-04-01T17:01:32'
|
||||
}
|
||||
|
||||
let state = (): NotificationsState => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
notifications: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: Notifications.actions,
|
||||
mutations: Notifications.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Notifications: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
},
|
||||
timelineSetting: {
|
||||
useMarker: {
|
||||
home: false,
|
||||
notifications: false,
|
||||
mentions: false
|
||||
}
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false,
|
||||
useMarkerTimeline: []
|
||||
}
|
||||
}
|
||||
|
||||
describe('Notifications', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchNotifications', () => {
|
||||
it('should be updated', async () => {
|
||||
const response = await store.dispatch('TimelineSpace/Contents/Notifications/fetchNotifications')
|
||||
expect(response).toEqual([notification1])
|
||||
expect(store.state.TimelineSpace.Contents.Notifications.notifications).toEqual([notification1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('lazyFetchNotifications', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
notifications: [notification1]
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should be updated', async () => {
|
||||
mockClient.getNotifications = () => {
|
||||
return new Promise<Response<Array<Entity.Notification>>>(resolve => {
|
||||
const res: Response<Array<Entity.Notification>> = {
|
||||
data: [notification2],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
await store.dispatch('TimelineSpace/Contents/Notifications/lazyFetchNotifications', notification1)
|
||||
expect(store.state.TimelineSpace.Contents.Notifications.lazyLoading).toEqual(false)
|
||||
expect(store.state.TimelineSpace.Contents.Notifications.notifications).toEqual([notification1, notification2])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,213 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import Public, { PublicState } from '@/store/TimelineSpace/Contents/Public'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const mockClient = {
|
||||
getPublicTimeline: () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status1],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
let state = (): PublicState => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [],
|
||||
unreads: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: Public.actions,
|
||||
mutations: Public.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Public: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Home', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('fetchPublicTimeline', () => {
|
||||
it('should be updated', async () => {
|
||||
const statuses = await store.dispatch('TimelineSpace/Contents/Public/fetchPublicTimeline')
|
||||
expect(statuses).toEqual([status1])
|
||||
expect(store.state.TimelineSpace.Contents.Public.timeline).toEqual([status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('lazyFetchTimeline', () => {
|
||||
describe('success', () => {
|
||||
beforeAll(() => {
|
||||
state = () => {
|
||||
return {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1],
|
||||
unreads: []
|
||||
}
|
||||
}
|
||||
})
|
||||
it('should be updated', async () => {
|
||||
mockClient.getPublicTimeline = () => {
|
||||
return new Promise<Response<Array<Entity.Status>>>(resolve => {
|
||||
const res: Response<Array<Entity.Status>> = {
|
||||
data: [status2],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
await store.dispatch('TimelineSpace/Contents/Public/lazyFetchTimeline', status1)
|
||||
expect(store.state.TimelineSpace.Contents.Public.lazyLoading).toEqual(false)
|
||||
expect(store.state.TimelineSpace.Contents.Public.timeline).toEqual([status1, status2])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,121 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import AccountStore, { AccountState } from '@/store/TimelineSpace/Contents/Search/Account'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
const mockClient = {
|
||||
searchAccount: () => {
|
||||
return new Promise<Response<Array<Entity.Account>>>(resolve => {
|
||||
const res: Response<Array<Entity.Account>> = {
|
||||
data: [account],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const state = (): AccountState => {
|
||||
return {
|
||||
results: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: AccountStore.actions,
|
||||
mutations: AccountStore.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const searchStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Account: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
state: {},
|
||||
mutations: {
|
||||
changeLoading: jest.fn()
|
||||
},
|
||||
actions: {},
|
||||
modules: {
|
||||
Search: searchStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
},
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
},
|
||||
sns: 'mastodon'
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Search/Account', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('search', () => {
|
||||
it('should be updated', async () => {
|
||||
await store.dispatch('TimelineSpace/Contents/Search/Account/search', 'query')
|
||||
expect(store.state.TimelineSpace.Contents.Search.Account.results).toEqual([account])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,109 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import TagStore, { TagState } from '@/store/TimelineSpace/Contents/Search/Tag'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const tag1: Entity.Tag = {
|
||||
name: 'tag1',
|
||||
url: 'http://example.com/tag1',
|
||||
history: null
|
||||
}
|
||||
|
||||
const mockClient = {
|
||||
search: () => {
|
||||
return new Promise<Response<Entity.Results>>(resolve => {
|
||||
const res: Response<Entity.Results> = {
|
||||
data: {
|
||||
accounts: [],
|
||||
statuses: [],
|
||||
hashtags: [tag1]
|
||||
},
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const state = (): TagState => {
|
||||
return {
|
||||
results: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: TagStore.actions,
|
||||
mutations: TagStore.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const searchStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Tag: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
state: {},
|
||||
mutations: {
|
||||
changeLoading: jest.fn()
|
||||
},
|
||||
actions: {},
|
||||
modules: {
|
||||
Search: searchStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
},
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
},
|
||||
sns: 'mastodon'
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Search/Tag', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('search', () => {
|
||||
it('should be updated', async () => {
|
||||
await store.dispatch('TimelineSpace/Contents/Search/Tag/search', 'query')
|
||||
expect(store.state.TimelineSpace.Contents.Search.Tag.results).toEqual([tag1])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,161 +0,0 @@
|
|||
import { Response, Entity } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import Toots, { TootsState } from '@/store/TimelineSpace/Contents/Search/Toots'
|
||||
import { RootState } from '@/store'
|
||||
|
||||
const mockClient = {
|
||||
search: () => {
|
||||
return new Promise<Response<Entity.Results>>(resolve => {
|
||||
const res: Response<Entity.Results> = {
|
||||
data: {
|
||||
accounts: [],
|
||||
statuses: [status],
|
||||
hashtags: []
|
||||
},
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
const status: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const state = (): TootsState => {
|
||||
return {
|
||||
results: []
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: Toots.actions,
|
||||
mutations: Toots.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const searchStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Toots: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
const contentsStore = () => ({
|
||||
namespaced: true,
|
||||
state: {},
|
||||
mutations: {
|
||||
changeLoading: jest.fn()
|
||||
},
|
||||
actions: {},
|
||||
modules: {
|
||||
Search: searchStore()
|
||||
}
|
||||
})
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
modules: {
|
||||
Contents: contentsStore()
|
||||
},
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
},
|
||||
sns: 'mastodon'
|
||||
}
|
||||
})
|
||||
|
||||
const appState = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
describe('Search/Account', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
TimelineSpace: timelineStore(),
|
||||
App: appState
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('search', () => {
|
||||
it('should be updated', async () => {
|
||||
await store.dispatch('TimelineSpace/Contents/Search/Toots/search', 'query')
|
||||
expect(store.state.TimelineSpace.Contents.Search.Toots.results).toEqual([status])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -40,16 +40,13 @@ const timelineStore = (account: Entity.Account | null) => ({
|
|||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
baseURL: 'https://example.com',
|
||||
domain: 'example.com',
|
||||
clientId: 'sampleId',
|
||||
clientSecret: 'sampleSecret',
|
||||
accessToken: 'sampleAccessToken',
|
||||
refreshToken: null,
|
||||
username: 'h3poteto',
|
||||
accountID: '1',
|
||||
avatar: null,
|
||||
order: 1
|
||||
id: 1,
|
||||
username: 'h3poteto'
|
||||
},
|
||||
server: {
|
||||
sns: 'mastodon',
|
||||
baseURL: 'https://example.com'
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
|
|
|
@ -49,10 +49,12 @@ const timelineStore = () => ({
|
|||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
accessToken: 'token'
|
||||
},
|
||||
sns: 'mastodon'
|
||||
server: {
|
||||
sns: 'mastodon',
|
||||
baseURL: 'http://localhost'
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
HeaderMenu: initStore()
|
||||
|
|
|
@ -84,9 +84,12 @@ const timelineStore = () => ({
|
|||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
_id: '0'
|
||||
id: 0,
|
||||
accessToken: 'token'
|
||||
},
|
||||
sns: 'mastodon'
|
||||
server: {
|
||||
sns: 'mastodon'
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
Modals: modalsStore()
|
||||
|
|
|
@ -42,8 +42,6 @@ const state = (): JumpState => {
|
|||
path: 'direct-messages'
|
||||
}
|
||||
],
|
||||
listChannelList: [],
|
||||
tagChannelList: [],
|
||||
selectedChannel: {
|
||||
name: i18n.t('side_menu.home'),
|
||||
path: 'home'
|
||||
|
@ -70,7 +68,7 @@ const timelineStore = () => ({
|
|||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
_id: '0'
|
||||
id: 0
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
|
|
|
@ -130,7 +130,12 @@ const timelineStore = () => ({
|
|||
namespaced: true,
|
||||
state: {
|
||||
account: {
|
||||
_id: '0'
|
||||
id: 0,
|
||||
accessToken: 'token'
|
||||
},
|
||||
server: {
|
||||
sns: 'mastodon',
|
||||
baseURL: 'http://localhost'
|
||||
}
|
||||
},
|
||||
modules: {
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
import { Entity, Response } from 'megalodon'
|
||||
import { createStore, Store } from 'vuex'
|
||||
import { ipcMain, ipcRenderer } from '~/spec/mock/electron'
|
||||
import SideMenu, { SideMenuState } from '~/src/renderer/store/TimelineSpace/SideMenu'
|
||||
import { LocalTag } from '~/src/types/localTag'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { RootState } from '@/store'
|
||||
;(window as any as MyWindow).ipcRenderer = ipcRenderer
|
||||
|
||||
const mockClient = {
|
||||
getLists: () => {
|
||||
return new Promise<Response<Entity.List[]>>(resolve => {
|
||||
const res: Response<Entity.List[]> = {
|
||||
data: [list1, list2],
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: {}
|
||||
}
|
||||
resolve(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('megalodon', () => ({
|
||||
...jest.requireActual<object>('megalodon'),
|
||||
default: jest.fn(() => mockClient),
|
||||
__esModule: true
|
||||
}))
|
||||
|
||||
// import mockedMegalodon from '~/spec/mock/megalodon'
|
||||
|
||||
const list1: Entity.List = {
|
||||
id: '1',
|
||||
title: 'example1'
|
||||
}
|
||||
|
||||
const list2: Entity.List = {
|
||||
id: '2',
|
||||
title: 'example2'
|
||||
}
|
||||
|
||||
const state = (): SideMenuState => {
|
||||
return {
|
||||
unreadHomeTimeline: false,
|
||||
unreadNotifications: false,
|
||||
unreadMentions: false,
|
||||
unreadLocalTimeline: false,
|
||||
unreadDirectMessagesTimeline: false,
|
||||
unreadPublicTimeline: false,
|
||||
unreadFollowRequests: false,
|
||||
lists: [],
|
||||
tags: [],
|
||||
collapse: false,
|
||||
enabledTimelines: {
|
||||
home: true,
|
||||
notification: true,
|
||||
mention: true,
|
||||
direct: true,
|
||||
favourite: true,
|
||||
bookmark: true,
|
||||
local: true,
|
||||
public: true,
|
||||
tag: true,
|
||||
list: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const initStore = () => {
|
||||
return {
|
||||
namespaced: true,
|
||||
state: state(),
|
||||
actions: SideMenu.actions,
|
||||
mutations: SideMenu.mutations
|
||||
}
|
||||
}
|
||||
|
||||
const appStore = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
proxyConfiguration: false
|
||||
}
|
||||
}
|
||||
|
||||
const timelineStore = () => ({
|
||||
namespaced: true,
|
||||
state: {
|
||||
sns: 'mastodon'
|
||||
},
|
||||
modules: {
|
||||
SideMenu: initStore()
|
||||
}
|
||||
})
|
||||
|
||||
describe('SideMenu', () => {
|
||||
let store: Store<RootState>
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore({
|
||||
modules: {
|
||||
App: appStore,
|
||||
TimelineSpace: timelineStore()
|
||||
}
|
||||
})
|
||||
// mockedMegalodon.mockClear()
|
||||
})
|
||||
|
||||
describe('fetchLists', () => {
|
||||
it('should be updated', async () => {
|
||||
// mockedMegalodon.mockImplementation(() => mockClient)
|
||||
const account = {
|
||||
accessToken: 'token',
|
||||
baseURL: 'http://localhost'
|
||||
}
|
||||
const lists = await store.dispatch('TimelineSpace/SideMenu/fetchLists', account)
|
||||
expect(store.state.TimelineSpace.SideMenu.lists).toEqual([list1, list2])
|
||||
expect(lists).toEqual([list1, list2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('clearUnread', () => {
|
||||
it('should be reset', () => {
|
||||
store.dispatch('TimelineSpace/SideMenu/clearUnread')
|
||||
expect(store.state.TimelineSpace.SideMenu.unreadHomeTimeline).toEqual(false)
|
||||
expect(store.state.TimelineSpace.SideMenu.unreadNotifications).toEqual(false)
|
||||
expect(store.state.TimelineSpace.SideMenu.unreadLocalTimeline).toEqual(false)
|
||||
expect(store.state.TimelineSpace.SideMenu.unreadDirectMessagesTimeline).toEqual(false)
|
||||
expect(store.state.TimelineSpace.SideMenu.unreadPublicTimeline).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('changeCollapse', () => {
|
||||
it('should be changed', () => {
|
||||
store.dispatch('TimelineSpace/SideMenu/changeCollapse', true)
|
||||
expect(store.state.TimelineSpace.SideMenu.collapse).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('readCollapse', () => {
|
||||
it('should be read', async () => {
|
||||
ipcMain.handle('get-collapse', () => {
|
||||
return true
|
||||
})
|
||||
await store.dispatch('TimelineSpace/SideMenu/readCollapse')
|
||||
expect(store.state.TimelineSpace.SideMenu.collapse).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('listTags', () => {
|
||||
it('should be listed', async () => {
|
||||
const tag1: LocalTag = {
|
||||
tagName: 'tag1'
|
||||
}
|
||||
const tag2: LocalTag = {
|
||||
tagName: 'tag2'
|
||||
}
|
||||
ipcMain.handle('list-hashtags', () => {
|
||||
return [tag1, tag2]
|
||||
})
|
||||
await store.dispatch('TimelineSpace/SideMenu/listTags')
|
||||
expect(store.state.TimelineSpace.SideMenu.tags).toEqual([tag1, tag2])
|
||||
})
|
||||
})
|
||||
})
|
|
@ -5,15 +5,17 @@ describe('Login', () => {
|
|||
let state: LoginState
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
selectedInstance: null,
|
||||
domain: null,
|
||||
searching: false,
|
||||
server: null,
|
||||
appData: null,
|
||||
sns: 'mastodon'
|
||||
}
|
||||
})
|
||||
describe('changeInstance', () => {
|
||||
it('should be changed', () => {
|
||||
Login.mutations![MUTATION_TYPES.CHANGE_INSTANCE](state, 'pleroma.io')
|
||||
expect(state.selectedInstance).toEqual('pleroma.io')
|
||||
Login.mutations![MUTATION_TYPES.CHANGE_DOMAIN](state, 'pleroma.io')
|
||||
expect(state.domain).toEqual('pleroma.io')
|
||||
})
|
||||
})
|
||||
describe('changeSearching', () => {
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import Account, { AccountState, MUTATION_TYPES } from '@/store/Preferences/Account'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
|
||||
const account: LocalAccount = {
|
||||
_id: 'sample',
|
||||
baseURL: 'http://example.com',
|
||||
domain: 'example.com',
|
||||
clientId: 'hoge',
|
||||
clientSecret: 'hogehoge',
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
username: null,
|
||||
accountId: null,
|
||||
avatar: null,
|
||||
order: 1
|
||||
}
|
||||
|
||||
describe('Preferences/Account', () => {
|
||||
describe('mutations', () => {
|
||||
let state: AccountState
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
accounts: [],
|
||||
accountLoading: false
|
||||
}
|
||||
})
|
||||
describe('updateAccounts', () => {
|
||||
it('should be updated', () => {
|
||||
Account.mutations![MUTATION_TYPES.UPDATE_ACCOUNTS](state, [account])
|
||||
expect(state.accounts).toEqual([account])
|
||||
})
|
||||
})
|
||||
describe('updateAccountLoading', () => {
|
||||
it('should be update', () => {
|
||||
Account.mutations![MUTATION_TYPES.UPDATE_ACCOUNT_LOADING](state, true)
|
||||
expect(state.accountLoading).toEqual(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,40 +0,0 @@
|
|||
import Theme from '~/src/constants/theme'
|
||||
import DisplayStyle from '~/src/constants/displayStyle'
|
||||
import TimeFormat from '~/src/constants/timeFormat'
|
||||
import { LightTheme } from '~/src/constants/themeColor'
|
||||
import DefaultFonts from '@/utils/fonts'
|
||||
import Appearance, { AppearanceState, MUTATION_TYPES } from '@/store/Preferences/Appearance'
|
||||
|
||||
describe('Preferences/Appearance', () => {
|
||||
let state: AppearanceState
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
appearance: {
|
||||
theme: Theme.Light.key,
|
||||
fontSize: 14,
|
||||
displayNameStyle: DisplayStyle.DisplayNameAndUsername.value,
|
||||
timeFormat: TimeFormat.Absolute.value,
|
||||
customThemeColor: LightTheme,
|
||||
font: DefaultFonts[0],
|
||||
tootPadding: 8
|
||||
},
|
||||
fonts: []
|
||||
}
|
||||
})
|
||||
describe('mutations', () => {
|
||||
describe('updateAppearance', () => {
|
||||
it('should be changed', () => {
|
||||
Appearance.mutations![MUTATION_TYPES.UPDATE_APPEARANCE](state, {
|
||||
theme: Theme.Dark.key
|
||||
})
|
||||
expect(state.appearance.theme).toEqual(Theme.Dark.key)
|
||||
})
|
||||
})
|
||||
describe('updateFonts', () => {
|
||||
it('should be changed', () => {
|
||||
Appearance.mutations![MUTATION_TYPES.UPDATE_FONTS](state, ['font'])
|
||||
expect(state.fonts).toEqual(['font'])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,38 +0,0 @@
|
|||
import General, { GeneralState, MUTATION_TYPES } from '@/store/Preferences/General'
|
||||
|
||||
describe('Preferences/General', () => {
|
||||
let state: GeneralState
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
general: {
|
||||
sound: {
|
||||
fav_rb: true,
|
||||
toot: true
|
||||
},
|
||||
timeline: {
|
||||
cw: false,
|
||||
nsfw: false,
|
||||
hideAllAttachments: false
|
||||
},
|
||||
other: {
|
||||
launch: false,
|
||||
hideOnLaunch: false
|
||||
}
|
||||
},
|
||||
loading: false
|
||||
}
|
||||
})
|
||||
|
||||
describe('mutations', () => {
|
||||
it('updateGeneral', () => {
|
||||
General.mutations![MUTATION_TYPES.UPDATE_GENERAL](state, {
|
||||
sound: {
|
||||
fav_rb: false,
|
||||
toot: false
|
||||
}
|
||||
})
|
||||
expect(state.general.sound.fav_rb).toEqual(false)
|
||||
expect(state.general.sound.toot).toEqual(false)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,33 +0,0 @@
|
|||
import Language, { LanguageState, MUTATION_TYPES } from '@/store/Preferences/Language'
|
||||
import DefaultLanguage from '~/src/constants/language'
|
||||
|
||||
describe('Preferences/Language', () => {
|
||||
let state: LanguageState
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
language: {
|
||||
language: DefaultLanguage.en.key,
|
||||
spellchecker: {
|
||||
enabled: true,
|
||||
languages: []
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
describe('mutations', () => {
|
||||
describe('updateLanguage', () => {
|
||||
it('should be updated', () => {
|
||||
Language.mutations![MUTATION_TYPES.UPDATE_LANGUAGE](state, {
|
||||
language: DefaultLanguage.ja.key
|
||||
})
|
||||
expect(state.language.language).toEqual(DefaultLanguage.ja.key)
|
||||
})
|
||||
})
|
||||
describe('changeLanguage', () => {
|
||||
it('should be changed', () => {
|
||||
Language.mutations![MUTATION_TYPES.CHANGE_LANGUAGE](state, DefaultLanguage.ja.key)
|
||||
expect(state.language.language).toEqual(DefaultLanguage.ja.key)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,50 +0,0 @@
|
|||
import Notification, { NotificationState, MUTATION_TYPES } from '@/store/Preferences/Notification'
|
||||
|
||||
describe('Preferences/Notification', () => {
|
||||
let state: NotificationState
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
notification: {
|
||||
notify: {
|
||||
reply: true,
|
||||
reblog: true,
|
||||
favourite: true,
|
||||
follow: true,
|
||||
follow_request: true,
|
||||
reaction: true,
|
||||
status: true,
|
||||
poll_vote: true,
|
||||
poll_expired: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
describe('mutations', () => {
|
||||
it('updateNotification', () => {
|
||||
Notification.mutations![MUTATION_TYPES.UPDATE_NOTIFICATION](state, {
|
||||
notify: {
|
||||
reply: false,
|
||||
reblog: false,
|
||||
favourite: false,
|
||||
follow: false,
|
||||
follow_request: false,
|
||||
reaction: false,
|
||||
status: false,
|
||||
poll_vote: false,
|
||||
poll_expired: false
|
||||
}
|
||||
})
|
||||
expect(state.notification.notify).toEqual({
|
||||
reply: false,
|
||||
reblog: false,
|
||||
favourite: false,
|
||||
follow: false,
|
||||
follow_request: false,
|
||||
reaction: false,
|
||||
status: false,
|
||||
poll_vote: false,
|
||||
poll_expired: false
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,4 +1,4 @@
|
|||
import TimelineSpace, { TimelineSpaceState, blankAccount, MUTATION_TYPES } from '~/src/renderer/store/TimelineSpace'
|
||||
import TimelineSpace, { TimelineSpaceState, MUTATION_TYPES } from '~/src/renderer/store/TimelineSpace'
|
||||
import { DefaultSetting } from '~/src/constants/initializer/setting'
|
||||
|
||||
describe('TimelineSpace', () => {
|
||||
|
@ -6,13 +6,12 @@ describe('TimelineSpace', () => {
|
|||
let state: TimelineSpaceState
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
account: blankAccount,
|
||||
bindingAccount: null,
|
||||
account: null,
|
||||
server: null,
|
||||
loading: false,
|
||||
emojis: [],
|
||||
tootMax: 500,
|
||||
timelineSetting: DefaultSetting.timeline,
|
||||
sns: 'mastodon',
|
||||
setting: DefaultSetting,
|
||||
filters: []
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,228 +0,0 @@
|
|||
import { Entity } from 'megalodon'
|
||||
import DirectMessages, { DirectMessagesState, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/DirectMessages'
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const rebloggedStatus: Entity.Status = {
|
||||
id: '3',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: status1,
|
||||
content: '',
|
||||
plain_content: null,
|
||||
created_at: '2019-03-31T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
describe('TimelineSpace/Contents/DirectMessages', () => {
|
||||
describe('mutations', () => {
|
||||
let state: DirectMessagesState
|
||||
|
||||
describe('deleteToot', () => {
|
||||
describe('message is not reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, status1]
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
DirectMessages.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('message is reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, rebloggedStatus]
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
DirectMessages.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('appendTimeline', () => {
|
||||
describe('heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, status1]
|
||||
}
|
||||
})
|
||||
it('should be updated timeline', () => {
|
||||
DirectMessages.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [rebloggedStatus, status2, status1]
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
DirectMessages.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('not heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [status2, status1]
|
||||
}
|
||||
})
|
||||
it('should be updated timeline', () => {
|
||||
DirectMessages.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [rebloggedStatus, status2, status1]
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
DirectMessages.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,235 +0,0 @@
|
|||
import { Entity } from 'megalodon'
|
||||
import Tag, { TagState, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Hashtag/Tag'
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const rebloggedStatus: Entity.Status = {
|
||||
id: '3',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: status1,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-31T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
describe('TimelineSpace/Contents/Hashtag/Tag', () => {
|
||||
describe('mutations', () => {
|
||||
let state: TagState
|
||||
|
||||
describe('deleteToot', () => {
|
||||
describe('message is not reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Tag.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('message is reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, rebloggedStatus],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Tag.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('appendTimeline', () => {
|
||||
describe('heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be updated timeline', () => {
|
||||
Tag.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [rebloggedStatus, status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
Tag.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('not heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
Tag.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([status2, status1])
|
||||
expect(state.unreads).toEqual([rebloggedStatus])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [rebloggedStatus, status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
Tag.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,363 +0,0 @@
|
|||
import { Entity } from 'megalodon'
|
||||
import Home, { HomeState, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Home'
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
describe('TimelineSpace/Contents/Home', () => {
|
||||
describe('mutations', () => {
|
||||
let state: HomeState
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
|
||||
describe('changeLazyLoading', () => {
|
||||
it('should be change', () => {
|
||||
Home.mutations![MUTATION_TYPES.CHANGE_LAZY_LOADING](state, true)
|
||||
expect(state.lazyLoading).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('changeHeading', () => {
|
||||
it('should be change', () => {
|
||||
Home.mutations![MUTATION_TYPES.CHANGE_HEADING](state, false)
|
||||
expect(state.heading).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('appendTimeline', () => {
|
||||
describe('heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should update timeline', () => {
|
||||
Home.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, status2)
|
||||
expect(state.timeline).toEqual([status2, status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, status1],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not update timeline', () => {
|
||||
Home.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, status2)
|
||||
expect(state.timeline).toEqual([status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('not heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [status1],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should update timeline', () => {
|
||||
Home.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, status2)
|
||||
expect(state.timeline).toEqual([status1])
|
||||
expect(state.unreads).toEqual([status2])
|
||||
})
|
||||
})
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [status2, status1],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not update timeline', () => {
|
||||
Home.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, status2)
|
||||
expect(state.timeline).toEqual([status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('insertTimeline', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be inserted', () => {
|
||||
Home.mutations![MUTATION_TYPES.INSERT_TIMELINE](state, [status2])
|
||||
expect(state.timeline).toEqual([status1, status2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateToot', () => {
|
||||
describe('message is not reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1, status2],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
const favouritedStatus: Entity.Status = Object.assign(status1, {
|
||||
favourited: true
|
||||
})
|
||||
it('should be updated', () => {
|
||||
Home.mutations![MUTATION_TYPES.UPDATE_TOOT](state, favouritedStatus)
|
||||
expect(state.timeline).toEqual([favouritedStatus, status2])
|
||||
})
|
||||
})
|
||||
describe('message is reblogged', () => {
|
||||
const rebloggedStatus: Entity.Status = {
|
||||
id: '3',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: status1,
|
||||
content: '',
|
||||
plain_content: null,
|
||||
created_at: '2019-03-31T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const favouritedStatus: Entity.Status = Object.assign(status1, {
|
||||
favourited: true
|
||||
})
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [rebloggedStatus, status2],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be updated', () => {
|
||||
Home.mutations![MUTATION_TYPES.UPDATE_TOOT](state, favouritedStatus)
|
||||
expect((state.timeline[0] as Entity.Status).reblog).not.toBeNull()
|
||||
expect((state.timeline[0] as Entity.Status).reblog!.favourited).toEqual(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteToot', () => {
|
||||
describe('message is not reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1, status2],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Home.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('message is reblogged', () => {
|
||||
beforeEach(() => {
|
||||
const rebloggedStatus: Entity.Status = {
|
||||
id: '3',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: status1,
|
||||
content: '',
|
||||
plain_content: null,
|
||||
created_at: '2019-03-31T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [rebloggedStatus, status2],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Home.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,235 +0,0 @@
|
|||
import { Entity } from 'megalodon'
|
||||
import Show, { ShowState, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Lists/Show'
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const rebloggedStatus: Entity.Status = {
|
||||
id: '3',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: status1,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-31T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
describe('TimelineSpace/Contents/Lists/Show', () => {
|
||||
describe('mutations', () => {
|
||||
let state: ShowState
|
||||
|
||||
describe('deleteToot', () => {
|
||||
describe('message is not reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Show.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('message is reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, rebloggedStatus],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Show.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('appendTimeline', () => {
|
||||
describe('heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be updated timeline', () => {
|
||||
Show.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [rebloggedStatus, status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
Show.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('not heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
Show.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([status2, status1])
|
||||
expect(state.unreads).toEqual([rebloggedStatus])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [rebloggedStatus, status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
Show.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,234 +0,0 @@
|
|||
import { Entity } from 'megalodon'
|
||||
import Local, { LocalState, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Local'
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const rebloggedStatus: Entity.Status = {
|
||||
id: '3',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: status1,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-31T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
describe('TimelineSpace/Contents/Local', () => {
|
||||
describe('mutations', () => {
|
||||
let state: LocalState
|
||||
|
||||
describe('deleteToot', () => {
|
||||
describe('message is not reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Local.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('message is reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, rebloggedStatus],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Local.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
})
|
||||
describe('appendTimeline', () => {
|
||||
describe('heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be updated timeline', () => {
|
||||
Local.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [rebloggedStatus, status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
Local.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('not heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be updated timeline', () => {
|
||||
Local.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([status2, status1])
|
||||
expect(state.unreads).toEqual([rebloggedStatus])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [rebloggedStatus, status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
Local.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,306 +0,0 @@
|
|||
import { Entity } from 'megalodon'
|
||||
import Mentions, { MentionsState, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Mentions'
|
||||
|
||||
const account1: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
const account2: Entity.Account = {
|
||||
id: '2',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@mstdn.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://mstdn.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account1,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account1,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const rebloggedStatus: Entity.Status = {
|
||||
id: '3',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account1,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: status2,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const notification1: Entity.Notification = {
|
||||
id: '1',
|
||||
account: account2,
|
||||
status: status1,
|
||||
type: 'mention',
|
||||
created_at: '2019-04-01T17:01:32'
|
||||
}
|
||||
|
||||
const notification2: Entity.Notification = {
|
||||
id: '2',
|
||||
account: account2,
|
||||
status: rebloggedStatus,
|
||||
type: 'mention',
|
||||
created_at: '2019-04-01T17:01:32'
|
||||
}
|
||||
|
||||
describe('TimelineSpace/Contents/Mentions', () => {
|
||||
describe('mutations', () => {
|
||||
let state: MentionsState
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
mentions: []
|
||||
}
|
||||
})
|
||||
|
||||
describe('appendMentions', () => {
|
||||
describe('heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
mentions: [notification1]
|
||||
}
|
||||
})
|
||||
it('should update mentions', () => {
|
||||
Mentions.mutations![MUTATION_TYPES.APPEND_MENTIONS](state, notification2)
|
||||
expect(state.mentions).toEqual([notification2, notification1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
mentions: [notification2, notification1]
|
||||
}
|
||||
})
|
||||
it('should not be updated mentions', () => {
|
||||
Mentions.mutations![MUTATION_TYPES.APPEND_MENTIONS](state, notification2)
|
||||
expect(state.mentions).toEqual([notification2, notification1])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('not heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
mentions: [notification1]
|
||||
}
|
||||
})
|
||||
it('should update mentions', () => {
|
||||
Mentions.mutations![MUTATION_TYPES.APPEND_MENTIONS](state, notification2)
|
||||
expect(state.mentions).toEqual([notification2, notification1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
mentions: [notification2, notification1]
|
||||
}
|
||||
})
|
||||
it('should not be updated mentions', () => {
|
||||
Mentions.mutations![MUTATION_TYPES.APPEND_MENTIONS](state, notification2)
|
||||
expect(state.mentions).toEqual([notification2, notification1])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('insertMentions', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
mentions: [notification2]
|
||||
}
|
||||
})
|
||||
it('should be inserted', () => {
|
||||
Mentions.mutations![MUTATION_TYPES.INSERT_MENTIONS](state, [notification1])
|
||||
expect(state.mentions).toEqual([notification2, notification1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateToot', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
mentions: [notification2, notification1]
|
||||
}
|
||||
})
|
||||
it('should be updated', () => {
|
||||
const favourited: Entity.Status = Object.assign(status1, {
|
||||
favourited: true
|
||||
})
|
||||
Mentions.mutations![MUTATION_TYPES.UPDATE_TOOT](state, favourited)
|
||||
expect((state.mentions[0] as Entity.Notification).status!.favourited).toEqual(null)
|
||||
expect((state.mentions[1] as Entity.Notification).status!.favourited).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteToot', () => {
|
||||
describe('message is not reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
mentions: [notification2, notification1]
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Mentions.mutations![MUTATION_TYPES.DELETE_TOOT](state, notification1.status!.id)
|
||||
expect(state.mentions.length).toEqual(1)
|
||||
})
|
||||
})
|
||||
describe('message is reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
mentions: [notification2, notification1]
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Mentions.mutations![MUTATION_TYPES.DELETE_TOOT](state, notification2.status!.id)
|
||||
expect(state.mentions.length).toEqual(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,259 +0,0 @@
|
|||
import { Entity } from 'megalodon'
|
||||
import Notifications, { NotificationsState, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Notifications'
|
||||
|
||||
const account1: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
const account2: Entity.Account = {
|
||||
id: '2',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@mstdn.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://mstdn.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account1,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account1,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const rebloggedStatus: Entity.Status = {
|
||||
id: '3',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account1,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: status2,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const notification1: Entity.Notification = {
|
||||
id: '1',
|
||||
account: account2,
|
||||
status: status1,
|
||||
type: 'favourite',
|
||||
created_at: '2019-04-01T17:01:32'
|
||||
}
|
||||
|
||||
const notification2: Entity.Notification = {
|
||||
id: '2',
|
||||
account: account2,
|
||||
status: rebloggedStatus,
|
||||
type: 'mention',
|
||||
created_at: '2019-04-01T17:01:32'
|
||||
}
|
||||
|
||||
describe('TimelineSpace/Contents/Notifications', () => {
|
||||
describe('mutations', () => {
|
||||
let state: NotificationsState
|
||||
|
||||
describe('deleteToot', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
notifications: [notification2, notification1]
|
||||
}
|
||||
})
|
||||
describe('message is not reblogged', () => {
|
||||
it('should be deleted', () => {
|
||||
Notifications.mutations![MUTATION_TYPES.DELETE_TOOT](state, notification1.status!.id)
|
||||
expect(state.notifications.length).toEqual(1)
|
||||
})
|
||||
})
|
||||
describe('message is reblogged', () => {
|
||||
it('should be deleted', () => {
|
||||
Notifications.mutations![MUTATION_TYPES.DELETE_TOOT](state, notification2.status!.id)
|
||||
expect(state.notifications.length).toEqual(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('appendTimeline', () => {
|
||||
describe('heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
notifications: [notification1]
|
||||
}
|
||||
})
|
||||
it('should update timeline', () => {
|
||||
Notifications.mutations![MUTATION_TYPES.APPEND_NOTIFICATIONS](state, notification2)
|
||||
expect(state.notifications).toEqual([notification2, notification1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
notifications: [notification2, notification1]
|
||||
}
|
||||
})
|
||||
it('should not update timeline', () => {
|
||||
Notifications.mutations![MUTATION_TYPES.APPEND_NOTIFICATIONS](state, notification2)
|
||||
expect(state.notifications).toEqual([notification2, notification1])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('not heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
notifications: [notification1]
|
||||
}
|
||||
})
|
||||
it('should update timeline', () => {
|
||||
Notifications.mutations![MUTATION_TYPES.APPEND_NOTIFICATIONS](state, notification2)
|
||||
expect(state.notifications).toEqual([notification2, notification1])
|
||||
})
|
||||
})
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
notifications: [notification2, notification1]
|
||||
}
|
||||
})
|
||||
it('should not update timeline', () => {
|
||||
Notifications.mutations![MUTATION_TYPES.APPEND_NOTIFICATIONS](state, notification2)
|
||||
expect(state.notifications).toEqual([notification2, notification1])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,235 +0,0 @@
|
|||
import { Entity } from 'megalodon'
|
||||
import Public, { PublicState, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Public'
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
const rebloggedStatus: Entity.Status = {
|
||||
id: '3',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: status1,
|
||||
content: '',
|
||||
plain_content: null,
|
||||
created_at: '2019-03-31T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
describe('TimelineSpace/Contents/Local', () => {
|
||||
describe('mutations', () => {
|
||||
let state: PublicState
|
||||
|
||||
describe('deleteToot', () => {
|
||||
describe('message is not reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Public.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('message is reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, rebloggedStatus],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Public.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('appendTimeline', () => {
|
||||
describe('heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be updated timeline', () => {
|
||||
Public.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [rebloggedStatus, status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
Public.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('not heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
Public.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([status2, status1])
|
||||
expect(state.unreads).toEqual([rebloggedStatus])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [rebloggedStatus, status2, status1],
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not be updated timeline', () => {
|
||||
Public.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, rebloggedStatus)
|
||||
expect(state.timeline).toEqual([rebloggedStatus, status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,110 +0,0 @@
|
|||
import i18n from '~/src/config/i18n'
|
||||
import Jump, { JumpState, MUTATION_TYPES, Channel } from '@/store/TimelineSpace/Modals/Jump'
|
||||
import { LocalTag } from '~/src/types/localTag'
|
||||
import { Entity } from 'megalodon'
|
||||
|
||||
describe('TimelineSpace/Modals/Jump', () => {
|
||||
describe('mutations', () => {
|
||||
let state: JumpState
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
modalOpen: true,
|
||||
channel: '',
|
||||
defaultChannelList: [
|
||||
{
|
||||
name: i18n.t('side_menu.home'),
|
||||
path: 'home'
|
||||
},
|
||||
{
|
||||
name: i18n.t('side_menu.notification'),
|
||||
path: 'notifications'
|
||||
},
|
||||
{
|
||||
name: i18n.t('side_menu.favourite'),
|
||||
path: 'favourites'
|
||||
},
|
||||
{
|
||||
name: i18n.t('side_menu.local'),
|
||||
path: 'local'
|
||||
},
|
||||
{
|
||||
name: i18n.t('side_menu.public'),
|
||||
path: 'public'
|
||||
},
|
||||
{
|
||||
name: i18n.t('side_menu.hashtag'),
|
||||
path: 'hashtag'
|
||||
},
|
||||
{
|
||||
name: i18n.t('side_menu.search'),
|
||||
path: 'search'
|
||||
},
|
||||
{
|
||||
name: i18n.t('side_menu.direct'),
|
||||
path: 'direct-messages'
|
||||
}
|
||||
],
|
||||
listChannelList: [],
|
||||
tagChannelList: [],
|
||||
selectedChannel: {
|
||||
name: i18n.t('side_menu.home'),
|
||||
path: 'home'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
describe('updateListChannel', () => {
|
||||
it('should be updated', () => {
|
||||
const admin: Entity.List = {
|
||||
id: '0',
|
||||
title: 'admin'
|
||||
}
|
||||
const engineer: Entity.List = {
|
||||
id: '1',
|
||||
title: 'engineer'
|
||||
}
|
||||
const designer: Entity.List = {
|
||||
id: '2',
|
||||
title: 'designer'
|
||||
}
|
||||
const channelList = [admin, engineer, designer]
|
||||
Jump.mutations![MUTATION_TYPES.UPDATE_LIST_CHANNEL](state, channelList)
|
||||
const adminChannel: Channel = {
|
||||
path: 'lists/0',
|
||||
name: '#admin'
|
||||
}
|
||||
const engineerChannel: Channel = {
|
||||
path: 'lists/1',
|
||||
name: '#engineer'
|
||||
}
|
||||
const designerChannel: Channel = {
|
||||
path: 'lists/2',
|
||||
name: '#designer'
|
||||
}
|
||||
expect(state.listChannelList).toEqual([adminChannel, engineerChannel, designerChannel])
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateTagChannel', () => {
|
||||
it('should be updated', () => {
|
||||
const whalebird: LocalTag = {
|
||||
tagName: 'whalebird'
|
||||
}
|
||||
const tqrk: LocalTag = {
|
||||
tagName: 'tqrk'
|
||||
}
|
||||
const channelList = [whalebird, tqrk]
|
||||
Jump.mutations![MUTATION_TYPES.UPDATE_TAG_CHANNEL](state, channelList)
|
||||
const whalebirdChannel: Channel = {
|
||||
name: '#whalebird',
|
||||
path: 'hashtag/whalebird'
|
||||
}
|
||||
const tqrkChannel: Channel = {
|
||||
name: '#tqrk',
|
||||
path: 'hashtag/tqrk'
|
||||
}
|
||||
expect(state.tagChannelList).toEqual([whalebirdChannel, tqrkChannel])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -50,7 +50,6 @@
|
|||
"expand": "Expand",
|
||||
"home": "Home",
|
||||
"notification": "Notification",
|
||||
"mention": "Mention",
|
||||
"direct": "Direct messages",
|
||||
"follow_requests": "Follow Requests",
|
||||
"favourite": "Favourite",
|
||||
|
@ -64,7 +63,6 @@
|
|||
"header_menu": {
|
||||
"home": "Home",
|
||||
"notification": "Notification",
|
||||
"mention": "Mention",
|
||||
"favourite": "Favourite",
|
||||
"bookmark": "Bookmark",
|
||||
"follow_requests": "Follow Requests",
|
||||
|
@ -463,10 +461,9 @@
|
|||
"timeline_fetch_error": "Failed to fetch timeline",
|
||||
"notification_fetch_error": "Failed to fetch notification",
|
||||
"favourite_fetch_error": "Failed to fetch favorite",
|
||||
"bookmark_fetch_error": "Failed to fetch bookmarks",
|
||||
"follow_request_accept_error": "Failed to accept the request",
|
||||
"follow_request_reject_error": "Failed to reject the request",
|
||||
"start_streaming_error": "Failed to start streaming",
|
||||
"start_all_streamings_error": "Failed to start streaming of {{domain}}",
|
||||
"attach_error": "Could not attach the file",
|
||||
"authorize_duplicate_error": "Can not login the same account of the same domain",
|
||||
"authorize_error": "Failed to authorize",
|
||||
|
|
|
@ -1,22 +1,7 @@
|
|||
import { Setting, Timeline, UnreadNotification, UseMarker } from '~/src/types/setting'
|
||||
|
||||
const unreadNotification: UnreadNotification = {
|
||||
direct: false,
|
||||
local: true,
|
||||
public: false
|
||||
}
|
||||
|
||||
const useMarker: UseMarker = {
|
||||
home: false,
|
||||
notifications: true
|
||||
}
|
||||
|
||||
const timeline: Timeline = {
|
||||
unreadNotification: unreadNotification,
|
||||
useMarker: useMarker
|
||||
}
|
||||
import { Setting } from '~/src/types/setting'
|
||||
|
||||
export const DefaultSetting: Setting = {
|
||||
accountID: '',
|
||||
timeline: timeline
|
||||
accountId: 0,
|
||||
markerHome: false,
|
||||
markerNotifications: true
|
||||
}
|
||||
|
|
|
@ -1,354 +0,0 @@
|
|||
import { isEmpty } from 'lodash'
|
||||
import generator, { detector, Entity, ProxyConfig } from 'megalodon'
|
||||
import Datastore from 'nedb'
|
||||
import log from 'electron-log'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
|
||||
export default class Account {
|
||||
private db: Datastore
|
||||
|
||||
constructor(db: Datastore) {
|
||||
this.db = db
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
await this.cleanup()
|
||||
await this.reorder()
|
||||
await this.updateUnique()
|
||||
}
|
||||
|
||||
updateUnique(): Promise<{}> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// At first, remove old index.
|
||||
this.db.removeIndex('order', err => {
|
||||
if (err) reject(err)
|
||||
// Add unique index.
|
||||
this.db.ensureIndex({ fieldName: 'order', unique: true, sparse: true }, err => {
|
||||
if (err) reject(err)
|
||||
resolve({})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder accounts, because sometimes the order of accounts is duplicated.
|
||||
*/
|
||||
async reorder() {
|
||||
const accounts = await this.listAllAccounts()
|
||||
await Promise.all(
|
||||
accounts.map(async (account, index) => {
|
||||
const update = await this.updateAccount(account._id!, Object.assign(account, { order: index + 1 }))
|
||||
return update
|
||||
})
|
||||
)
|
||||
const ordered = await this.listAllAccounts()
|
||||
return ordered
|
||||
}
|
||||
|
||||
/**
|
||||
* Check order of all accounts, and fix if order is negative value or over the length.
|
||||
*/
|
||||
async cleanup() {
|
||||
const accounts = await this.listAccounts()
|
||||
if (accounts.length < 1) {
|
||||
return accounts.length
|
||||
}
|
||||
if (accounts[0].order < 1 || accounts[accounts.length - 1].order > accounts.length) {
|
||||
await Promise.all(
|
||||
accounts.map(async (element, index) => {
|
||||
const update = await this.updateAccount(element._id!, Object.assign(element, { order: index + 1 }))
|
||||
return update
|
||||
})
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
insertAccount(localAccount: LocalAccount): Promise<LocalAccount> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.insert<LocalAccount>(localAccount, (err, doc) => {
|
||||
if (err) return reject(err)
|
||||
if (isEmpty(doc)) return reject(new EmptyRecordError('empty'))
|
||||
resolve(doc)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* List up all accounts either authenticated or not authenticated.
|
||||
*/
|
||||
listAllAccounts(order = 1): Promise<Array<LocalAccount>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db
|
||||
.find<LocalAccount>({})
|
||||
.sort({ order: order })
|
||||
.exec((err, docs) => {
|
||||
if (err) return reject(err)
|
||||
if (isEmpty(docs)) return reject(new EmptyRecordError('empty'))
|
||||
resolve(docs)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* List up authenticated accounts.
|
||||
*/
|
||||
listAccounts(): Promise<Array<LocalAccount>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db
|
||||
.find<LocalAccount>({ $and: [{ accessToken: { $ne: '' } }, { accessToken: { $ne: null } }] })
|
||||
.sort({ order: 1 })
|
||||
.exec((err, docs) => {
|
||||
if (err) return reject(err)
|
||||
if (isEmpty(docs)) return reject(new EmptyRecordError('empty'))
|
||||
resolve(docs)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Get the last account.
|
||||
async lastAccount(): Promise<LocalAccount> {
|
||||
const accounts = await this.listAllAccounts(-1)
|
||||
return accounts[0]
|
||||
}
|
||||
|
||||
getAccount(id: string): Promise<LocalAccount> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.findOne<LocalAccount>(
|
||||
{
|
||||
_id: id
|
||||
},
|
||||
(err, doc) => {
|
||||
if (err) return reject(err)
|
||||
if (isEmpty(doc)) return reject(new EmptyRecordError('empty'))
|
||||
resolve(doc)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
searchAccount(obj: any): Promise<LocalAccount> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.findOne<LocalAccount>(obj, (err, doc) => {
|
||||
if (err) return reject(err)
|
||||
if (isEmpty(doc)) return reject(new EmptyRecordError('empty'))
|
||||
resolve(doc)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
searchAccounts(obj: any, order = 1): Promise<Array<LocalAccount>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db
|
||||
.find<LocalAccount>(obj)
|
||||
.sort({ order: order })
|
||||
.exec((err, docs) => {
|
||||
if (err) return reject(err)
|
||||
resolve(docs)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
updateAccount(id: string, obj: any): Promise<LocalAccount> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.update(
|
||||
{
|
||||
_id: id
|
||||
},
|
||||
{ $set: Object.assign(obj, { _id: id }) },
|
||||
{ multi: true },
|
||||
(err, _numReplaced) => {
|
||||
if (err) return reject(err)
|
||||
this.db.findOne<LocalAccount>(
|
||||
{
|
||||
_id: id
|
||||
},
|
||||
(err, doc) => {
|
||||
if (err) return reject(err)
|
||||
if (isEmpty(doc)) return reject(new EmptyRecordError('empty'))
|
||||
resolve(doc)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
removeAccount(id: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.remove(
|
||||
{
|
||||
_id: id
|
||||
},
|
||||
{ multi: true },
|
||||
(err, _numRemoved) => {
|
||||
if (err) return reject(err)
|
||||
resolve(id)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
removeAll(): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.remove({}, { multi: true }, (err, numRemoved) => {
|
||||
if (err) return reject(err)
|
||||
resolve(numRemoved)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async forwardAccount(ac: LocalAccount): Promise<LocalAccount | {}> {
|
||||
// Find account which is the previous of the target account.
|
||||
const accounts = await this.searchAccounts({ order: { $lt: ac.order } }, -1).catch(err => {
|
||||
console.log(err)
|
||||
return []
|
||||
})
|
||||
if (accounts.length < 1) {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
const previousAccount = accounts[0]
|
||||
const targetOrder = ac.order
|
||||
const previousOrder = previousAccount.order
|
||||
|
||||
// At first, we need to update the previous account with dummy order.
|
||||
// Because this column is uniqued, so can not update with same order.
|
||||
await this.updateAccount(
|
||||
previousAccount._id!,
|
||||
Object.assign(previousAccount, {
|
||||
order: -1
|
||||
})
|
||||
)
|
||||
// Change order of the target account.
|
||||
const updated = await this.updateAccount(
|
||||
ac._id!,
|
||||
Object.assign(ac, {
|
||||
order: previousOrder
|
||||
})
|
||||
)
|
||||
// Update the previous account with right order.
|
||||
await this.updateAccount(
|
||||
previousAccount._id!,
|
||||
Object.assign(previousAccount, {
|
||||
order: targetOrder
|
||||
})
|
||||
)
|
||||
return updated
|
||||
}
|
||||
|
||||
async backwardAccount(ac: LocalAccount): Promise<LocalAccount | {}> {
|
||||
// Find account which is the next of the target account.
|
||||
const accounts = await this.searchAccounts({ order: { $gt: ac.order } }, 1).catch(err => {
|
||||
console.log(err)
|
||||
return []
|
||||
})
|
||||
if (accounts.length < 1) {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
const nextAccount = accounts[0]
|
||||
const targetOrder = ac.order
|
||||
const nextOrder = nextAccount.order
|
||||
|
||||
// At first, we need to update the next account with dummy order.
|
||||
// Because this column is uniqued, so can not update with same order.
|
||||
await this.updateAccount(
|
||||
nextAccount._id!,
|
||||
Object.assign(nextAccount, {
|
||||
order: -1
|
||||
})
|
||||
)
|
||||
// Change order of the target account/
|
||||
const updated = await this.updateAccount(
|
||||
ac._id!,
|
||||
Object.assign(ac, {
|
||||
order: nextOrder
|
||||
})
|
||||
)
|
||||
// Update the next account with right order.
|
||||
await this.updateAccount(
|
||||
nextAccount._id!,
|
||||
Object.assign(nextAccount, {
|
||||
order: targetOrder
|
||||
})
|
||||
)
|
||||
return updated
|
||||
}
|
||||
|
||||
async refreshAccounts(proxy: ProxyConfig | false): Promise<Array<LocalAccount>> {
|
||||
const accounts = await this.listAccounts()
|
||||
if (accounts.length < 1) {
|
||||
return accounts
|
||||
}
|
||||
const results = await Promise.all(
|
||||
accounts.map(async account => {
|
||||
const refresh = await this.refresh(account, proxy)
|
||||
return refresh
|
||||
})
|
||||
)
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
* refresh: Refresh an account which is already saved at local
|
||||
* @param {LocalAccount} account is an local account
|
||||
* @return {LocalAccount} updated account
|
||||
*/
|
||||
async refresh(account: LocalAccount, proxy: ProxyConfig | false): Promise<LocalAccount> {
|
||||
const sns = await detector(account.baseURL, proxy)
|
||||
let client = generator(sns, account.baseURL, account.accessToken, 'Whalebird', proxy)
|
||||
let json = {}
|
||||
try {
|
||||
const res = await client.verifyAccountCredentials()
|
||||
json = {
|
||||
username: res.data.username,
|
||||
accountId: res.data.id,
|
||||
avatar: res.data.avatar
|
||||
}
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
log.info('Get new access token using refresh token...')
|
||||
// If failed to fetch account, get new access token using refresh token.
|
||||
if (!account.refreshToken) {
|
||||
throw new RefreshTokenDoesNotExist()
|
||||
}
|
||||
const token = await client.refreshToken(account.clientId, account.clientSecret, account.refreshToken)
|
||||
client = generator(sns, account.baseURL, token.access_token, 'Whalebird', proxy)
|
||||
const res = await client.verifyAccountCredentials()
|
||||
json = {
|
||||
username: res.data.username,
|
||||
accountId: res.data.id,
|
||||
avatar: res.data.avatar,
|
||||
accessToken: token.accessToken,
|
||||
refreshToken: token.refreshToken
|
||||
}
|
||||
}
|
||||
return this.updateAccount(account._id!, json)
|
||||
}
|
||||
|
||||
// Confirm the access token, and check duplicate
|
||||
async fetchAccount(
|
||||
sns: 'mastodon' | 'pleroma' | 'misskey',
|
||||
account: LocalAccount,
|
||||
accessToken: string,
|
||||
proxy: ProxyConfig | false
|
||||
): Promise<Entity.Account> {
|
||||
const client = generator(sns, account.baseURL, accessToken, 'Whalebird', proxy)
|
||||
const res = await client.verifyAccountCredentials()
|
||||
const query = {
|
||||
baseURL: account.baseURL,
|
||||
username: res.data.username
|
||||
}
|
||||
const duplicates = await this.searchAccounts(query)
|
||||
if (duplicates.length > 0) {
|
||||
throw new DuplicateRecordError(`${res.data.username}@${account.baseURL} is duplicated`)
|
||||
}
|
||||
return res.data
|
||||
}
|
||||
}
|
||||
|
||||
class EmptyRecordError extends Error {}
|
||||
|
||||
class DuplicateRecordError extends Error {}
|
||||
|
||||
class RefreshTokenDoesNotExist extends Error {}
|
119
src/main/auth.ts
119
src/main/auth.ts
|
@ -1,119 +0,0 @@
|
|||
import generator, { ProxyConfig } from 'megalodon'
|
||||
import crypto from 'crypto'
|
||||
import Account from './account'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
|
||||
const appName = 'Whalebird'
|
||||
const appURL = 'https://whalebird.social'
|
||||
|
||||
export default class Authentication {
|
||||
private db: Account
|
||||
private baseURL: string | null = null
|
||||
private domain: string | null = null
|
||||
private clientId: string | null = null
|
||||
private clientSecret: string | null = null
|
||||
private sessionToken: string | null = null
|
||||
private protocol: 'http' | 'https'
|
||||
|
||||
constructor(accountDB: Account) {
|
||||
this.db = accountDB
|
||||
this.protocol = 'https'
|
||||
}
|
||||
|
||||
setOtherInstance(domain: string) {
|
||||
this.baseURL = `${this.protocol}://${domain}`
|
||||
this.domain = domain
|
||||
this.clientId = null
|
||||
this.clientSecret = null
|
||||
}
|
||||
|
||||
async getAuthorizationUrl(
|
||||
sns: 'mastodon' | 'pleroma' | 'misskey',
|
||||
domain: string = 'mastodon.social',
|
||||
proxy: ProxyConfig | false
|
||||
): Promise<string> {
|
||||
this.setOtherInstance(domain)
|
||||
if (!this.baseURL || !this.domain) {
|
||||
throw new Error('domain is required')
|
||||
}
|
||||
const client = generator(sns, this.baseURL, null, 'Whalebird', proxy)
|
||||
const res = await client.registerApp(appName, {
|
||||
website: appURL
|
||||
})
|
||||
this.clientId = res.clientId
|
||||
this.clientSecret = res.clientSecret
|
||||
this.sessionToken = res.session_token
|
||||
|
||||
const order = await this.db
|
||||
.lastAccount()
|
||||
.then(account => account.order + 1)
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
return 1
|
||||
})
|
||||
const local: LocalAccount = {
|
||||
baseURL: this.baseURL,
|
||||
domain: this.domain,
|
||||
clientId: this.clientId,
|
||||
clientSecret: this.clientSecret,
|
||||
accessToken: null,
|
||||
refreshToken: null,
|
||||
username: null,
|
||||
accountId: null,
|
||||
avatar: null,
|
||||
order: order
|
||||
}
|
||||
await this.db.insertAccount(local)
|
||||
if (res.url === null) {
|
||||
throw new AuthenticationURLError('Can not get url')
|
||||
}
|
||||
return res.url
|
||||
}
|
||||
|
||||
async getAndUpdateAccessToken(sns: 'mastodon' | 'pleroma' | 'misskey', code: string | null, proxy: ProxyConfig | false): Promise<string> {
|
||||
if (!this.baseURL) {
|
||||
throw new Error('domain is required')
|
||||
}
|
||||
if (!this.clientSecret) {
|
||||
throw new Error('client secret is required')
|
||||
}
|
||||
const client = generator(sns, this.baseURL, null, 'Whalebird', proxy)
|
||||
|
||||
// In Misskey session token is required instead of authentication code.
|
||||
let authCode = code
|
||||
if (!code) {
|
||||
authCode = this.sessionToken
|
||||
}
|
||||
if (!authCode) {
|
||||
throw new Error('auth code is required')
|
||||
}
|
||||
const tokenData = await client.fetchAccessToken(this.clientId, this.clientSecret, authCode, 'urn:ietf:wg:oauth:2.0:oob')
|
||||
const search = {
|
||||
baseURL: this.baseURL,
|
||||
domain: this.domain,
|
||||
clientId: this.clientId,
|
||||
clientSecret: this.clientSecret
|
||||
}
|
||||
const rec = await this.db.searchAccount(search)
|
||||
let accessToken = tokenData.accessToken
|
||||
// In misskey, access token is sha256(userToken + clientSecret)
|
||||
if (sns === 'misskey') {
|
||||
accessToken = crypto
|
||||
.createHash('sha256')
|
||||
.update(tokenData.accessToken + this.clientSecret, 'utf8')
|
||||
.digest('hex')
|
||||
}
|
||||
const refreshToken = tokenData.refreshToken
|
||||
const data = await this.db.fetchAccount(sns, rec, accessToken, proxy)
|
||||
await this.db.updateAccount(rec._id!, {
|
||||
username: data.username,
|
||||
accountId: data.id,
|
||||
avatar: data.avatar,
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken
|
||||
})
|
||||
return accessToken
|
||||
}
|
||||
}
|
||||
|
||||
class AuthenticationURLError extends Error {}
|
|
@ -1,48 +0,0 @@
|
|||
import { isEmpty } from 'lodash'
|
||||
import Datastore from 'nedb'
|
||||
import fs from 'fs'
|
||||
import { CachedAccount } from '~/src/types/cachedAccount'
|
||||
|
||||
export default class AccountCache {
|
||||
private db: Datastore
|
||||
|
||||
constructor(path: string) {
|
||||
this.db = new Datastore({
|
||||
filename: path,
|
||||
autoload: true,
|
||||
onload: (err: Error) => {
|
||||
if (err) {
|
||||
fs.unlink(path, err => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
listAccounts(ownerID: string): Promise<Array<CachedAccount>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.find<CachedAccount>({ owner_id: ownerID }, (err, docs) => {
|
||||
if (err) return reject(err)
|
||||
resolve(docs)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
insertAccount(ownerID: string, acct: string): Promise<CachedAccount> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// At first confirm records for unique.
|
||||
this.db.findOne<CachedAccount>({ owner_id: ownerID, acct: acct }, (err, doc) => {
|
||||
if (err) return err
|
||||
// Ignore error for unique constraints.
|
||||
if (!isEmpty(doc)) return err
|
||||
return this.db.insert<CachedAccount>({ owner_id: ownerID, acct: acct }, (err, doc) => {
|
||||
if (err) return reject(err)
|
||||
return resolve(doc)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
import Datastore from 'nedb'
|
||||
import fs from 'fs'
|
||||
import { LocalTag } from '~/src/types/localTag'
|
||||
|
||||
export default class HashtagCache {
|
||||
private db: Datastore
|
||||
|
||||
constructor(path: string) {
|
||||
this.db = new Datastore({
|
||||
filename: path,
|
||||
autoload: true,
|
||||
onload: (err: Error) => {
|
||||
if (err) {
|
||||
fs.unlink(path, err => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
this.db.ensureIndex({ fieldName: 'tagName', unique: true, sparse: true }, err => {
|
||||
if (err) console.error(err)
|
||||
})
|
||||
}
|
||||
|
||||
listTags(): Promise<Array<LocalTag>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.find<LocalTag>({}, (err, docs) => {
|
||||
if (err) return reject(err)
|
||||
resolve(docs)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
insertHashtag(tag: string): Promise<LocalTag> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Ignore error for unique constraints.
|
||||
this.db.insert({ tagName: tag }, (err, doc) => {
|
||||
if (err) return reject(err)
|
||||
resolve(doc)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,22 +1,69 @@
|
|||
import Loki from 'lokijs'
|
||||
import sqlite3 from 'sqlite3'
|
||||
|
||||
const newDB = (file: string): Promise<Loki> => {
|
||||
return new Promise(resolve => {
|
||||
const databaseInitializer = () => {
|
||||
let markers = db.getCollection('markers')
|
||||
if (markers === null) {
|
||||
markers = db.addCollection('markers')
|
||||
const newDB = (file: string): sqlite3.Database => {
|
||||
const db = new sqlite3.Database(file, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE)
|
||||
|
||||
// migration
|
||||
db.serialize(() => {
|
||||
db.run(
|
||||
'CREATE TABLE IF NOT EXISTS accounts(\
|
||||
id INTEGER PRIMARY KEY, \
|
||||
username TEXT NOT NULL, \
|
||||
account_id TEXT NOT NULL, \
|
||||
avatar TEXT NOT NULL, \
|
||||
client_id TEXT DEFAULT NULL, \
|
||||
client_secret TEXT NOT NULL, \
|
||||
access_token TEXT NOT NULL, \
|
||||
refresh_token TEXT DEFAULT NULL, \
|
||||
sort INTEGER UNIQUE NOT NULL)',
|
||||
err => {
|
||||
if (err) {
|
||||
console.error('failed to create accounts: ', err)
|
||||
}
|
||||
}
|
||||
resolve(db)
|
||||
}
|
||||
|
||||
const db = new Loki(file, {
|
||||
autoload: true,
|
||||
autosave: true,
|
||||
autosaveInterval: 4000,
|
||||
autoloadCallback: databaseInitializer
|
||||
})
|
||||
)
|
||||
db.run(
|
||||
'CREATE TABLE IF NOT EXISTS servers(\
|
||||
id INTEGER PRIMARY KEY, \
|
||||
domain TEXT NOT NULL, \
|
||||
base_url TEXT NOT NULL, \
|
||||
sns TEXT NOT NULL, \
|
||||
account_id INTEGER UNIQUE DEFAULT NULL, \
|
||||
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE)',
|
||||
err => {
|
||||
if (err) {
|
||||
console.error('failed to create servers: ', err)
|
||||
}
|
||||
}
|
||||
)
|
||||
db.run(
|
||||
'CREATE TABLE IF NOT EXISTS hashtags(\
|
||||
id INTEGER PRIMARY KEY, \
|
||||
tag TEXT NOT NULL, \
|
||||
account_id INTEGER UNIQUE NOT NULL, \
|
||||
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE)',
|
||||
err => {
|
||||
if (err) {
|
||||
console.error('failed to create hashtags: ', err)
|
||||
}
|
||||
}
|
||||
)
|
||||
db.run(
|
||||
'CREATE TABLE IF NOT EXISTS settings(\
|
||||
id INTEGER PRIMARY KEY, \
|
||||
account_id INTEGER UNIQUE NOT NULL, \
|
||||
marker_home BOOLEAN NOT NULL DEFAULT false, \
|
||||
marker_notifications BOOLEAN NOT NULL DEFAULT true, \
|
||||
FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE)',
|
||||
err => {
|
||||
if (err) {
|
||||
console.error('failed to create settings: ', err)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
export default newDB
|
||||
|
|
|
@ -0,0 +1,285 @@
|
|||
import sqlite3 from 'sqlite3'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~src/types/localServer'
|
||||
|
||||
export const insertAccount = (
|
||||
db: sqlite3.Database,
|
||||
username: string,
|
||||
accountId: string,
|
||||
avatar: string,
|
||||
clientId: string,
|
||||
clientSecret: string,
|
||||
accessToken: string,
|
||||
refreshToken: string | null,
|
||||
server: LocalServer
|
||||
): Promise<LocalAccount> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.run('BEGIN TRANSACTION')
|
||||
|
||||
db.get('SELECT * FROM accounts ORDER BY sort DESC', (err, row) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
let order = 1
|
||||
if (row) {
|
||||
order = row.sort + 1
|
||||
}
|
||||
db.run(
|
||||
'INSERT INTO accounts(username, account_id, avatar, client_id, client_secret, access_token, refresh_token, sort) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[username, accountId, avatar, clientId, clientSecret, accessToken, refreshToken, order],
|
||||
function (err) {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
const id = this.lastID
|
||||
|
||||
db.run('UPDATE servers SET account_id = ? WHERE id = ?', [id, server.id], err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
db.run('COMMIT')
|
||||
|
||||
resolve({
|
||||
id,
|
||||
username,
|
||||
accountId,
|
||||
avatar,
|
||||
clientId,
|
||||
clientSecret,
|
||||
accessToken,
|
||||
refreshToken,
|
||||
order
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* List up authenticated accounts.
|
||||
*/
|
||||
export const listAccounts = (db: sqlite3.Database): Promise<Array<[LocalAccount, LocalServer]>> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all(
|
||||
'SELECT \
|
||||
accounts.id as id, \
|
||||
accounts.username as username, \
|
||||
accounts.account_id as remote_account_id, \
|
||||
accounts.avatar as avatar, \
|
||||
accounts.client_id as client_id, \
|
||||
accounts.client_secret as client_secret, \
|
||||
accounts.access_token as access_token, \
|
||||
accounts.refresh_token as refresh_token, \
|
||||
accounts.sort as sort, \
|
||||
servers.id as server_id, \
|
||||
servers.base_url as base_url, \
|
||||
servers.domain as domain, \
|
||||
servers.sns as sns, \
|
||||
servers.account_id as account_id \
|
||||
FROM accounts INNER JOIN servers ON servers.account_id = accounts.id ORDER BY accounts.sort',
|
||||
(err, rows) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
resolve(
|
||||
rows.map(r => [
|
||||
{
|
||||
id: r.id,
|
||||
username: r.username,
|
||||
accountId: r.remote_account_id,
|
||||
avatar: r.avatar,
|
||||
clientId: r.client_id,
|
||||
clientSecret: r.client_secret,
|
||||
accessToken: r.access_token,
|
||||
refreshToken: r.refresh_token,
|
||||
order: r.sort
|
||||
} as LocalAccount,
|
||||
{
|
||||
id: r.server_id,
|
||||
baseURL: r.base_url,
|
||||
domain: r.domain,
|
||||
sns: r.sns,
|
||||
accountId: r.account_id
|
||||
} as LocalServer
|
||||
])
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export const getAccount = (db: sqlite3.Database, id: number): Promise<[LocalAccount, LocalServer]> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(
|
||||
'SELECT \
|
||||
accounts.id as id, \
|
||||
accounts.username as username, \
|
||||
accounts.account_id as remote_account_id, \
|
||||
accounts.avatar as avatar, \
|
||||
accounts.client_id as client_id, \
|
||||
accounts.client_secret as client_secret, \
|
||||
accounts.access_token as access_token, \
|
||||
accounts.refresh_token as refresh_token, \
|
||||
accounts.sort as sort, \
|
||||
servers.id as server_id, \
|
||||
servers.base_url as base_url, \
|
||||
servers.domain as domain, \
|
||||
servers.sns as sns, \
|
||||
servers.account_id as account_id \
|
||||
FROM accounts INNER JOIN servers ON servers.account_id = accounts.id WHERE accounts.id = ?',
|
||||
id,
|
||||
(err, r) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
resolve([
|
||||
{
|
||||
id: r.id,
|
||||
username: r.username,
|
||||
accountId: r.remote_account_id,
|
||||
avatar: r.avatar,
|
||||
clientId: r.client_id,
|
||||
clientSecret: r.client_secret,
|
||||
accessToken: r.access_token,
|
||||
refreshToken: r.refresh_token,
|
||||
order: r.sort
|
||||
} as LocalAccount,
|
||||
{
|
||||
id: r.server_id,
|
||||
baseURL: r.base_url,
|
||||
domain: r.domain,
|
||||
sns: r.sns,
|
||||
accountId: r.account_id
|
||||
} as LocalServer
|
||||
])
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export const removeAccount = (db: sqlite3.Database, id: number): Promise<null> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run('DELETE FROM accounts WHERE id = ?', id, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const removeAllAccounts = (db: sqlite3.Database): Promise<null> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run('DELETE FROM accounts', err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const forwardAccount = (db: sqlite3.Database, id: number): Promise<null> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.run('BEGIN TRANSACTION')
|
||||
|
||||
db.all('SELECT * FROM accounts ORDER BY sort', (err, rows) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
db.run('ROLLBACK TRANSACTION')
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
const index = rows.findIndex(r => r.id === id)
|
||||
if (index < 0 || index >= rows.length - 1) {
|
||||
db.run('ROLLBACK TRANSACTION')
|
||||
return resolve(null)
|
||||
}
|
||||
const target = rows[index + 1]
|
||||
const base = rows[index]
|
||||
|
||||
db.serialize(() => {
|
||||
db.run('UPDATE accounts SET sort = ? WHERE id = ?', [-100, base.id], err => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
db.run('ROLLBACK TRANSACTION')
|
||||
return reject(err)
|
||||
}
|
||||
})
|
||||
db.run('UPDATE accounts SET sort = ? WHERE id = ?', [base.sort, target.id], err => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
db.run('ROLLBACK TRANSACTION')
|
||||
return reject(err)
|
||||
}
|
||||
})
|
||||
db.run('UPDATE accounts SET sort = ? WHERE id = ?', [target.sort, base.id], err => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
db.run('ROLLBACK TRANSACTION')
|
||||
return reject(err)
|
||||
}
|
||||
db.run('COMMIT')
|
||||
return resolve(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const backwardAccount = (db: sqlite3.Database, id: number): Promise<null> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.run('BEGIN TRANSACTION')
|
||||
|
||||
db.all('SELECT * FROM accounts ORDER BY sort', (err, rows) => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
db.run('ROLLBACK TRANSACTION')
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
const index = rows.findIndex(r => r.id === id)
|
||||
if (index < 1) {
|
||||
db.run('ROLLBACK TRANSACTION')
|
||||
return resolve(null)
|
||||
}
|
||||
const target = rows[index - 1]
|
||||
const base = rows[index]
|
||||
|
||||
db.serialize(() => {
|
||||
db.run('UPDATE accounts SET sort = ? WHERE id = ?', [-100, base.id], err => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
db.run('ROLLBACK TRANSACTION')
|
||||
return reject(err)
|
||||
}
|
||||
})
|
||||
db.run('UPDATE accounts SET sort = ? WHERE id = ?', [base.sort, target.id], err => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
db.run('ROLLBACK TRANSACTION')
|
||||
return reject(err)
|
||||
}
|
||||
})
|
||||
db.run('UPDATE accounts SET sort = ? WHERE id = ?', [target.sort, base.id], err => {
|
||||
if (err) {
|
||||
console.error(err)
|
||||
db.run('ROLLBACK TRANSACTION')
|
||||
return reject(err)
|
||||
}
|
||||
db.run('COMMIT')
|
||||
return resolve(null)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import sqlite3 from 'sqlite3'
|
||||
import { LocalTag } from '~/src/types/localTag'
|
||||
|
||||
export const listTags = (db: sqlite3.Database, accountId: number): Promise<Array<LocalTag>> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.all('SELECT * FROM hashtags WHERE account_id = ?', accountId, (err, rows) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
resolve(
|
||||
rows.map(r => ({
|
||||
id: r.id,
|
||||
tagName: r.tag,
|
||||
accountId: r.account_id
|
||||
}))
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const insertTag = (db: sqlite3.Database, accountId: number, tag: string): Promise<LocalTag> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.run('BEGIN TRANSACTION')
|
||||
|
||||
db.get('SELECT * FROM hashtags WHERE id = ? AND tag = ?', [accountId, tag], (err, row) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
if (row) {
|
||||
resolve({
|
||||
id: row.id,
|
||||
tagName: row.tag,
|
||||
accountId: row.account_id
|
||||
})
|
||||
}
|
||||
|
||||
db.run('INSERT INTO hashtags(tag, account_id) VALUES (?, ?)', [accountId, tag], function (err) {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
db.run('COMMIT')
|
||||
resolve({
|
||||
id: this.lastID,
|
||||
tagName: tag,
|
||||
accountId: accountId
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const removeTag = (db: sqlite3.Database, tag: LocalTag): Promise<null> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run('DELETE FROM hashtags WHERE id = ?', tag.id, err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
resolve(null)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import sqlite3 from 'sqlite3'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
|
||||
export const insertServer = (
|
||||
db: sqlite3.Database,
|
||||
baseURL: string,
|
||||
domain: string,
|
||||
sns: 'mastodon' | 'pleroma' | 'misskey',
|
||||
accountId: number | null
|
||||
): Promise<LocalServer> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.serialize(() => {
|
||||
db.run('INSERT INTO servers(domain, base_url, sns, account_id) values (?, ?, ?, ?)', [domain, baseURL, sns, accountId], function (
|
||||
err
|
||||
) {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
resolve({
|
||||
id: this.lastID,
|
||||
baseURL,
|
||||
domain,
|
||||
sns,
|
||||
accountId
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import sqlite3 from 'sqlite3'
|
||||
import { Setting } from '~/src/types/setting'
|
||||
import { DefaultSetting } from '~/src/constants/initializer/setting'
|
||||
|
||||
export const getSetting = (db: sqlite3.Database, accountId: number): Promise<Setting> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get('SELECT * FROM settings WHERE account_id = ?', accountId, (err, row) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
if (row) {
|
||||
resolve({
|
||||
accountId: row.account_id,
|
||||
markerHome: Boolean(row.marker_home),
|
||||
markerNotifications: Boolean(row.marker_notifications)
|
||||
})
|
||||
}
|
||||
resolve(DefaultSetting)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const createOrUpdateSetting = (db: sqlite3.Database, setting: Setting): Promise<Setting> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get('SELECT * FROM settings WHERE account_id = ?', setting.accountId, (err, row) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
if (row) {
|
||||
db.run(
|
||||
'UPDATE settings SET marker_home = ?, marker_notifications = ? WHERE account_id = ?',
|
||||
[setting.markerHome, setting.markerNotifications, setting.accountId],
|
||||
err => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
resolve(setting)
|
||||
}
|
||||
)
|
||||
resolve(setting)
|
||||
} else {
|
||||
db.run(
|
||||
'INSERT INTO settings(account_id, marker_home, marker_notifications) VALUES (?, ?, ?)',
|
||||
[setting.accountId, setting.markerHome, setting.markerNotifications],
|
||||
function (err) {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
resolve(setting)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
import Datastore from 'nedb'
|
||||
import { LocalTag } from '~/src/types/localTag'
|
||||
|
||||
export default class Hashtags {
|
||||
private db: Datastore
|
||||
|
||||
constructor(db: Datastore) {
|
||||
this.db = db
|
||||
this.db.ensureIndex({ fieldName: 'tagName', unique: true }, _ => {})
|
||||
}
|
||||
|
||||
listTags(): Promise<Array<LocalTag>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.find<LocalTag>({}, (err, docs) => {
|
||||
if (err) return reject(err)
|
||||
resolve(docs)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
insertTag(tag: string): Promise<LocalTag> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.insert({ tagName: tag }, (err, doc) => {
|
||||
if (err) return reject(err)
|
||||
resolve(doc)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
removeTag(localTag: LocalTag): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.remove(
|
||||
{
|
||||
tagName: localTag.tagName
|
||||
},
|
||||
{ multi: true },
|
||||
(err, numRemoved) => {
|
||||
if (err) return reject(err)
|
||||
resolve(numRemoved)
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,77 +0,0 @@
|
|||
import { isEmpty } from 'lodash'
|
||||
import Loki, { Collection } from 'lokijs'
|
||||
import { LocalMarker } from '~/src/types/localMarker'
|
||||
|
||||
export default class Marker {
|
||||
private markers: Collection<any>
|
||||
|
||||
constructor(db: Loki) {
|
||||
this.markers = db.getCollection('markers')
|
||||
}
|
||||
|
||||
private insert(marker: LocalMarker): Promise<LocalMarker> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const doc: LocalMarker = this.markers.insert(marker)
|
||||
resolve(doc)
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private update(marker: LocalMarker): Promise<LocalMarker> {
|
||||
// @ts-ignore
|
||||
return new Promise((resolve, reject) => {
|
||||
// eslint-disable-line no-unused-vars
|
||||
try {
|
||||
this.markers.findAndUpdate(
|
||||
{
|
||||
owner_id: { $eq: marker.owner_id },
|
||||
timeline: { $eq: marker.timeline }
|
||||
},
|
||||
(item: LocalMarker) => {
|
||||
item.last_read_id = marker.last_read_id
|
||||
}
|
||||
)
|
||||
return this.get(marker.owner_id, marker.timeline)
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public async save(marker: LocalMarker): Promise<LocalMarker> {
|
||||
return this.get(marker.owner_id, marker.timeline).then(l => {
|
||||
if (isEmpty(l)) return this.insert(marker)
|
||||
return this.update(marker)
|
||||
})
|
||||
}
|
||||
|
||||
public async get(owner_id: string, timeline: 'home' | 'notifications' | 'mentions'): Promise<LocalMarker | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const doc: LocalMarker | null = this.markers.findOne({
|
||||
owner_id: { $eq: owner_id },
|
||||
timeline: { $eq: timeline }
|
||||
})
|
||||
resolve(doc)
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public async list(owner_id: string): Promise<Array<LocalMarker>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const docs: Array<LocalMarker> = this.markers.find({
|
||||
owner_id: { $eq: owner_id }
|
||||
})
|
||||
resolve(docs)
|
||||
} catch (err) {
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
import storage from 'electron-json-storage'
|
||||
import log from 'electron-log'
|
||||
import objectAssignDeep from 'object-assign-deep'
|
||||
import { BaseSettings, Setting } from '~/src/types/setting'
|
||||
import { DefaultSetting } from '~/src/constants/initializer/setting'
|
||||
import { isEmpty } from 'lodash'
|
||||
|
||||
export default class Settings {
|
||||
private path: string
|
||||
|
||||
constructor(path: string) {
|
||||
this.path = path
|
||||
}
|
||||
|
||||
public async _load(): Promise<BaseSettings> {
|
||||
try {
|
||||
const settings = await this._get()
|
||||
if (isEmpty(settings)) {
|
||||
return []
|
||||
}
|
||||
return settings
|
||||
} catch (err) {
|
||||
log.error(err)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
public async get(accountID: string): Promise<Setting> {
|
||||
const current = await this._load()
|
||||
const find: Setting | undefined = current.find(d => {
|
||||
return d.accountID === accountID
|
||||
})
|
||||
if (find) {
|
||||
return objectAssignDeep({}, DefaultSetting, find)
|
||||
}
|
||||
return objectAssignDeep({}, DefaultSetting, {
|
||||
accountID: accountID
|
||||
})
|
||||
}
|
||||
|
||||
private _get(): Promise<BaseSettings> {
|
||||
return new Promise((resolve, reject) => {
|
||||
storage.get(this.path, (err, data) => {
|
||||
if (err) return reject(err)
|
||||
return resolve(data as BaseSettings)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
private _save(data: BaseSettings): Promise<BaseSettings> {
|
||||
return new Promise((resolve, reject) => {
|
||||
storage.set(this.path, data, err => {
|
||||
if (err) return reject(err)
|
||||
return resolve(data)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public async update(obj: Setting): Promise<BaseSettings> {
|
||||
const current = await this._load()
|
||||
const find = current.find(d => {
|
||||
return d.accountID === obj.accountID
|
||||
})
|
||||
if (find) {
|
||||
const data = current.map(d => {
|
||||
if (d.accountID !== obj.accountID) {
|
||||
return d
|
||||
}
|
||||
const newData = objectAssignDeep({}, d, obj)
|
||||
return newData
|
||||
})
|
||||
const result = await this._save(data)
|
||||
return result
|
||||
} else {
|
||||
const data = current.concat([obj])
|
||||
const result = await this._save(data)
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
import generator, { detector, ProxyConfig } from 'megalodon'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { EnabledTimelines } from '~/src/types/enabledTimelines'
|
||||
|
||||
const confirm = async (account: LocalAccount, proxy: ProxyConfig | false) => {
|
||||
const sns = await detector(account.baseURL, proxy)
|
||||
const client = generator(sns, account.baseURL, account.accessToken, 'Whalebird', proxy)
|
||||
|
||||
let timelines: EnabledTimelines = {
|
||||
home: true,
|
||||
notification: true,
|
||||
mention: true,
|
||||
direct: true,
|
||||
favourite: true,
|
||||
bookmark: true,
|
||||
local: true,
|
||||
public: true,
|
||||
tag: true,
|
||||
list: true
|
||||
}
|
||||
|
||||
const notification = async () => {
|
||||
return client.getNotifications({ limit: 1 }).catch(() => {
|
||||
timelines = { ...timelines, notification: false, mention: false }
|
||||
})
|
||||
}
|
||||
const direct = async () => {
|
||||
return client.getConversationTimeline({ limit: 1 }).catch(() => {
|
||||
timelines = { ...timelines, direct: false }
|
||||
})
|
||||
}
|
||||
const favourite = async () => {
|
||||
return client.getFavourites({ limit: 1 }).catch(() => {
|
||||
timelines = { ...timelines, favourite: false }
|
||||
})
|
||||
}
|
||||
const bookmark = async () => {
|
||||
return client.getBookmarks({ limit: 1 }).catch(() => {
|
||||
timelines = { ...timelines, bookmark: false }
|
||||
})
|
||||
}
|
||||
const local = async () => {
|
||||
return client.getLocalTimeline({ limit: 1 }).catch(() => {
|
||||
timelines = { ...timelines, local: false }
|
||||
})
|
||||
}
|
||||
const pub = async () => {
|
||||
return client.getPublicTimeline({ limit: 1 }).catch(() => {
|
||||
timelines = { ...timelines, public: false }
|
||||
})
|
||||
}
|
||||
const tag = async () => {
|
||||
return client.getTagTimeline('whalebird', { limit: 1 }).catch(() => {
|
||||
timelines = { ...timelines, tag: false }
|
||||
})
|
||||
}
|
||||
await Promise.all([notification(), direct(), favourite(), bookmark(), local(), pub(), tag()])
|
||||
|
||||
return timelines
|
||||
}
|
||||
|
||||
export default confirm
|
|
@ -1,16 +1,18 @@
|
|||
import generator, { MegalodonInterface, WebSocketInterface, Entity, ProxyConfig } from 'megalodon'
|
||||
import log from 'electron-log'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~src/types/localServer'
|
||||
|
||||
const StreamingURL = async (
|
||||
sns: 'mastodon' | 'pleroma' | 'misskey',
|
||||
account: LocalAccount,
|
||||
server: LocalServer,
|
||||
proxy: ProxyConfig | false
|
||||
): Promise<string> => {
|
||||
if (!account.accessToken) {
|
||||
throw new Error('access token is empty')
|
||||
}
|
||||
const client = generator(sns, account.baseURL, account.accessToken, 'Whalebird', proxy)
|
||||
const client = generator(sns, server.baseURL, account.accessToken, 'Whalebird', proxy)
|
||||
const res = await client.getInstance()
|
||||
return res.data.urls.streaming_api
|
||||
}
|
||||
|
|
|
@ -1,191 +0,0 @@
|
|||
<template>
|
||||
<div id="authorize">
|
||||
<div>
|
||||
<el-header>
|
||||
<el-row>
|
||||
<el-col :span="24" class="close">
|
||||
<el-button class="close-button" link @click="close">
|
||||
<font-awesome-icon icon="xmark"></font-awesome-icon>
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-header>
|
||||
<el-main>
|
||||
<div class="authorization-url">
|
||||
<p>{{ $t('authorize.manually_1') }}</p>
|
||||
<p>{{ $t('authorize.manually_2') }}</p>
|
||||
<p class="url">{{ $route.query.url }}</p>
|
||||
</div>
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="authorizeForm"
|
||||
label-width="120px"
|
||||
label-position="top"
|
||||
class="authorize-form"
|
||||
v-on:submit.prevent="authorizeSubmit"
|
||||
>
|
||||
<p v-if="sns === 'misskey'">{{ $t('authorize.misskey_label') }}</p>
|
||||
<el-form-item :label="$t('authorize.code_label')" v-else>
|
||||
<el-input v-model="authorizeForm.code"></el-input>
|
||||
</el-form-item>
|
||||
<!-- Dummy form to guard submitting with enter -->
|
||||
<el-form-item class="hidden">
|
||||
<el-input></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="submit">
|
||||
<el-button
|
||||
v-loading="submitting"
|
||||
type="primary"
|
||||
class="authorize"
|
||||
element-loading-background="rgba(0, 0, 0, 0.8)"
|
||||
@click="authorizeSubmit"
|
||||
>
|
||||
{{ $t('authorize.submit') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive, toRefs, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18next } from 'vue3-i18next'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { useStore } from '@/store'
|
||||
import { ACTION_TYPES } from '@/store/Authorize'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'authorize',
|
||||
props: {
|
||||
url: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
sns: {
|
||||
type: String,
|
||||
default: 'mastodon'
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const space = 'Authorize'
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
const i18n = useI18next()
|
||||
const { escape } = useMagicKeys()
|
||||
|
||||
const { url, sns } = toRefs(props)
|
||||
|
||||
const authorizeForm = reactive({
|
||||
code: null
|
||||
})
|
||||
const submitting = ref<boolean>(false)
|
||||
|
||||
onMounted(() => {
|
||||
console.log(url.value)
|
||||
})
|
||||
|
||||
whenever(escape, () => {
|
||||
close()
|
||||
})
|
||||
|
||||
const authorizeSubmit = () => {
|
||||
submitting.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.SUBMIT}`, {
|
||||
code: authorizeForm.code,
|
||||
sns: sns.value
|
||||
})
|
||||
.finally(() => {
|
||||
submitting.value = false
|
||||
})
|
||||
.then(id => {
|
||||
router.push({ path: `/${id}/home` })
|
||||
})
|
||||
.catch(err => {
|
||||
if (err.name === 'DuplicateRecordError') {
|
||||
ElMessage({
|
||||
message: i18n.t('message.authorize_duplicate_error'),
|
||||
type: 'error'
|
||||
})
|
||||
} else {
|
||||
ElMessage({
|
||||
message: i18n.t('message.authorize_error'),
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
router.push({ path: '/', query: { redirect: 'home' } })
|
||||
}
|
||||
|
||||
return {
|
||||
authorizeForm,
|
||||
submitting,
|
||||
authorizeSubmit,
|
||||
close
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#authorize {
|
||||
background-color: #292f3f;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
min-height: 100%;
|
||||
|
||||
.close {
|
||||
text-align: right;
|
||||
|
||||
.close-button {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.authorization-url {
|
||||
margin: 0 auto 64px;
|
||||
max-width: 80%;
|
||||
|
||||
.url {
|
||||
color: #909399;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.authorize-form {
|
||||
width: 500px;
|
||||
margin: 0 auto;
|
||||
|
||||
.authorize {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.authorize-form :deep() {
|
||||
.el-form-item__label {
|
||||
color: #f0f3f9;
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
background-color: #373d48;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: #373d48;
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -12,16 +12,15 @@
|
|||
role="menubar"
|
||||
>
|
||||
<el-menu-item
|
||||
:index="`/${account._id}/`"
|
||||
:route="{ path: `/${account._id}/home` }"
|
||||
v-for="(account, _index) in accounts"
|
||||
v-bind:key="account._id"
|
||||
:index="`/${account.id}/`"
|
||||
:route="{ path: `/${account.id}/home` }"
|
||||
v-for="([account, server], _index) in accounts"
|
||||
:key="account.id"
|
||||
role="menuitem"
|
||||
>
|
||||
<font-awesome-icon icon="circle-user" v-if="account.avatar === undefined || account.avatar === null || account.avatar === ''" />
|
||||
<FailoverImg v-else :src="account.avatar" class="avatar" :title="account.username + '@' + account.domain" />
|
||||
<FailoverImg :src="`${account.baseURL}/favicon.ico`" :failoverSrc="`${account.baseURL}/favicon.png`" class="instance-icon" />
|
||||
<span slot="title">{{ account.domain }}</span>
|
||||
<FailoverImg :src="account.avatar" class="avatar" :title="account.username + '@' + server.domain" />
|
||||
<FailoverImg :src="`${server.baseURL}/favicon.ico`" :failoverSrc="`${server.baseURL}/favicon.png`" class="instance-icon" />
|
||||
<span slot="title">{{ server.domain }}</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/login" :title="$t('global_header.add_new_account')" role="menuitem" class="add-new-account">
|
||||
<font-awesome-icon icon="plus" />
|
||||
|
@ -37,11 +36,8 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, computed, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useI18next } from 'vue3-i18next'
|
||||
import { useStore } from '@/store'
|
||||
import FailoverImg from '@/components/atoms/FailoverImg.vue'
|
||||
import { StreamingError } from '~/src/errors/streamingError'
|
||||
import { ACTION_TYPES } from '@/store/GlobalHeader'
|
||||
|
||||
export default defineComponent({
|
||||
|
@ -54,7 +50,6 @@ export default defineComponent({
|
|||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const i18n = useI18next()
|
||||
|
||||
const accounts = computed(() => store.state.GlobalHeader.accounts)
|
||||
const hide = computed(() => store.state.GlobalHeader.hide)
|
||||
|
@ -67,20 +62,10 @@ export default defineComponent({
|
|||
|
||||
const initialize = async () => {
|
||||
await store
|
||||
.dispatch(`${space}/initLoad`)
|
||||
.dispatch(`${space}/${ACTION_TYPES.INIT_LOAD}`)
|
||||
.then(accounts => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.START_STREAMINGS}`).catch(err => {
|
||||
if (err instanceof StreamingError) {
|
||||
ElMessage({
|
||||
message: i18n.t('message.start_all_streamings_error', {
|
||||
domain: err.domain
|
||||
}),
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
})
|
||||
if (route.params.id === undefined) {
|
||||
router.push({ path: `/${accounts[0]._id}/home` })
|
||||
router.push({ path: `/${accounts[0][0].id}/home` })
|
||||
}
|
||||
})
|
||||
.catch(_ => {
|
||||
|
|
|
@ -10,29 +10,32 @@
|
|||
</el-row>
|
||||
</el-header>
|
||||
<el-container>
|
||||
<div></div>
|
||||
<login-form></login-form>
|
||||
<login-form v-if="appData === null" />
|
||||
<authorize v-else />
|
||||
</el-container>
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from '@/store'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import LoginForm from './Login/LoginForm.vue'
|
||||
import Authorize from './Login/Authorize.vue'
|
||||
import { ACTION_TYPES } from '@/store/Login'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'login',
|
||||
components: { LoginForm },
|
||||
components: { LoginForm, Authorize },
|
||||
setup() {
|
||||
const space = 'Login'
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
const { escape } = useMagicKeys()
|
||||
|
||||
const appData = computed(() => store.state.Login.appData)
|
||||
|
||||
whenever(escape, () => {
|
||||
close()
|
||||
})
|
||||
|
@ -46,7 +49,8 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
return {
|
||||
close
|
||||
close,
|
||||
appData
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
<template>
|
||||
<el-main id="authorize">
|
||||
<div class="authorization-url">
|
||||
<p>{{ $t('authorize.manually_1') }}</p>
|
||||
<p>{{ $t('authorize.manually_2') }}</p>
|
||||
<p class="url">{{ $route.query.url }}</p>
|
||||
</div>
|
||||
<el-form
|
||||
ref="form"
|
||||
:model="authorizeForm"
|
||||
label-width="120px"
|
||||
label-position="top"
|
||||
class="authorize-form"
|
||||
@submit.prevent="authorizeSubmit"
|
||||
>
|
||||
<p v-if="sns === 'misskey'">{{ $t('authorize.misskey_label') }}</p>
|
||||
<el-form-item :label="$t('authorize.code_label')" v-else>
|
||||
<el-input v-model="authorizeForm.code"></el-input>
|
||||
</el-form-item>
|
||||
<!-- Dummy form to guard submitting with enter -->
|
||||
<el-form-item class="hidden">
|
||||
<el-input></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item class="submit">
|
||||
<el-button
|
||||
v-loading="submitting"
|
||||
type="primary"
|
||||
class="authorize"
|
||||
element-loading-background="rgba(0, 0, 0, 0.8)"
|
||||
@click="authorizeSubmit"
|
||||
>
|
||||
{{ $t('authorize.submit') }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-main>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, reactive, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18next } from 'vue3-i18next'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { useStore } from '@/store'
|
||||
import { ACTION_TYPES } from '@/store/Login'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Authorize',
|
||||
setup() {
|
||||
const space = 'Login'
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
const i18n = useI18next()
|
||||
const { escape } = useMagicKeys()
|
||||
|
||||
const sns = computed(() => store.state.Login.sns)
|
||||
|
||||
const authorizeForm = reactive({
|
||||
code: null
|
||||
})
|
||||
const submitting = ref<boolean>(false)
|
||||
|
||||
whenever(escape, () => {
|
||||
close()
|
||||
})
|
||||
|
||||
const authorizeSubmit = () => {
|
||||
submitting.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.AUTHORIZE}`, authorizeForm.code)
|
||||
.finally(() => {
|
||||
submitting.value = false
|
||||
})
|
||||
.then(id => {
|
||||
router.push({ path: `/${id}/home` })
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err)
|
||||
ElMessage({
|
||||
message: i18n.t('message.authorize_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
router.push({ path: '/', query: { redirect: 'home' } })
|
||||
}
|
||||
|
||||
return {
|
||||
authorizeForm,
|
||||
submitting,
|
||||
authorizeSubmit,
|
||||
close,
|
||||
sns
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#authorize {
|
||||
background-color: #292f3f;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
min-height: 100%;
|
||||
|
||||
.close {
|
||||
text-align: right;
|
||||
|
||||
.close-button {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.authorization-url {
|
||||
margin: 0 auto 64px;
|
||||
max-width: 80%;
|
||||
|
||||
.url {
|
||||
color: #909399;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.authorize-form {
|
||||
width: 500px;
|
||||
margin: 0 auto;
|
||||
|
||||
.authorize {
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.authorize-form :deep() {
|
||||
.el-form-item__label {
|
||||
color: #f0f3f9;
|
||||
}
|
||||
|
||||
.el-input__inner {
|
||||
background-color: #373d48;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: #373d48;
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -40,7 +40,6 @@
|
|||
import { defineComponent, computed, reactive, ref } from 'vue'
|
||||
import { useI18next } from 'vue3-i18next'
|
||||
import { ElLoading, ElMessage, FormInstance, FormRules } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from '@/store'
|
||||
import { domainFormat } from '@/utils/validator'
|
||||
import { ACTION_TYPES } from '@/store/Login'
|
||||
|
@ -51,16 +50,14 @@ export default defineComponent({
|
|||
const space = 'Login'
|
||||
const store = useStore()
|
||||
const i18n = useI18next()
|
||||
const router = useRouter()
|
||||
|
||||
const form = reactive({
|
||||
domainName: ''
|
||||
})
|
||||
const loginFormRef = ref<FormInstance>()
|
||||
|
||||
const selectedInstance = computed(() => store.state.Login.selectedInstance)
|
||||
const selectedInstance = computed(() => store.state.Login.domain)
|
||||
const searching = computed(() => store.state.Login.searching)
|
||||
const sns = computed(() => store.state.Login.sns)
|
||||
const allowLogin = computed(() => selectedInstance.value && form.domainName === selectedInstance.value)
|
||||
const rules = reactive<FormRules>({
|
||||
domainName: [
|
||||
|
@ -77,31 +74,24 @@ export default defineComponent({
|
|||
]
|
||||
})
|
||||
|
||||
const login = () => {
|
||||
const login = async () => {
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: i18n.t('message.loading'),
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.FETCH_LOGIN}`)
|
||||
.then(url => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.PAGE_BACK}`)
|
||||
router.push({
|
||||
path: '/authorize',
|
||||
query: { url: url, sns: sns.value }
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.authorize_url_error'),
|
||||
type: 'error'
|
||||
})
|
||||
console.error(err)
|
||||
})
|
||||
.finally(() => {
|
||||
loading.close()
|
||||
try {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.ADD_SERVER}`)
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.ADD_APP}`)
|
||||
} catch (err) {
|
||||
ElMessage({
|
||||
message: i18n.t('message.authorize_url_error'),
|
||||
type: 'error'
|
||||
})
|
||||
console.error(err)
|
||||
} finally {
|
||||
loading.close()
|
||||
}
|
||||
}
|
||||
|
||||
const confirm = async (formEl: FormInstance | undefined) => {
|
||||
|
|
|
@ -16,7 +16,16 @@
|
|||
<el-table-column prop="domain" :label="$t('preferences.account.domain')"> </el-table-column>
|
||||
<el-table-column :label="$t('preferences.account.association')">
|
||||
<template #default="scope">
|
||||
<el-button class="action" link @click.prevent="removeAccount(scope.$index, accounts)">
|
||||
<el-button
|
||||
class="action"
|
||||
link
|
||||
@click.prevent="
|
||||
removeAccount(
|
||||
scope.$index,
|
||||
accounts.map(a => a.id)
|
||||
)
|
||||
"
|
||||
>
|
||||
<font-awesome-icon icon="xmark" />
|
||||
{{ $t('preferences.account.remove_association') }}
|
||||
</el-button>
|
||||
|
@ -25,12 +34,30 @@
|
|||
<el-table-column :label="$t('preferences.account.order')" width="60">
|
||||
<template #default="scope">
|
||||
<div class="allow-up">
|
||||
<el-button class="arrow-up action" link @click.prevent="forward(scope.$index, accounts)">
|
||||
<el-button
|
||||
class="arrow-up action"
|
||||
link
|
||||
@click.prevent="
|
||||
backward(
|
||||
scope.$index,
|
||||
accounts.map(a => a.id)
|
||||
)
|
||||
"
|
||||
>
|
||||
<font-awesome-icon icon="arrow-up" />
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="allow-down">
|
||||
<el-button class="arrow-down action" link @click.prevent="backward(scope.$index, accounts)">
|
||||
<el-button
|
||||
class="arrow-down action"
|
||||
link
|
||||
@click.prevent="
|
||||
forward(
|
||||
scope.$index,
|
||||
accounts.map(a => a.id)
|
||||
)
|
||||
"
|
||||
>
|
||||
<font-awesome-icon icon="arrow-down" />
|
||||
</el-button>
|
||||
</div>
|
||||
|
@ -61,7 +88,6 @@ import { useRouter } from 'vue-router'
|
|||
import { useStore } from '@/store'
|
||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/Preferences/Account'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'account',
|
||||
|
@ -71,7 +97,13 @@ export default defineComponent({
|
|||
const i18n = useI18next()
|
||||
const router = useRouter()
|
||||
|
||||
const accounts = computed(() => store.state.Preferences.Account.accounts)
|
||||
const accounts = computed(() =>
|
||||
store.state.Preferences.Account.accounts.map(([a, s]) => ({
|
||||
id: a.id,
|
||||
username: a.username,
|
||||
domain: s.domain
|
||||
}))
|
||||
)
|
||||
const accountLoading = computed(() => store.state.Preferences.Account.accountLoading)
|
||||
const backgroundColor = computed(() => store.state.App.theme.background_color)
|
||||
|
||||
|
@ -93,7 +125,7 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
const removeAccount = (index: number, accounts: Array<LocalAccount>) => {
|
||||
const removeAccount = (index: number, accounts: Array<number>) => {
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.REMOVE_ACCOUNT}`, accounts[index])
|
||||
.then(() => {
|
||||
|
@ -107,13 +139,13 @@ export default defineComponent({
|
|||
})
|
||||
}
|
||||
|
||||
const forward = (index: number, accounts: Array<LocalAccount>) => {
|
||||
const forward = (index: number, accounts: Array<number>) => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.FORWARD_ACCOUNT}`, accounts[index]).then(() => {
|
||||
loadAccounts()
|
||||
})
|
||||
}
|
||||
|
||||
const backward = (index: number, accounts: Array<LocalAccount>) => {
|
||||
const backward = (index: number, accounts: Array<number>) => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.BACKWARD_ACCOUNT}`, accounts[index]).then(() => {
|
||||
loadAccounts()
|
||||
})
|
||||
|
|
|
@ -64,7 +64,7 @@ export default defineComponent({
|
|||
const activeRoute = computed(() => route.path)
|
||||
|
||||
onMounted(() => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_ACCOUNT_ID}`, id.value)
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_ACCOUNT_ID}`, parseInt(id.value as string))
|
||||
router.push(`/${id.value}/settings/general`)
|
||||
})
|
||||
|
||||
|
|
|
@ -1,23 +1,6 @@
|
|||
<template>
|
||||
<div id="timeline">
|
||||
<h2>{{ $t('settings.timeline.title') }}</h2>
|
||||
<el-form class="unread-notification section" size="default" label-position="right" label-width="250px">
|
||||
<h3>{{ $t('settings.timeline.unread_notification.title') }}</h3>
|
||||
<p class="description">
|
||||
{{ $t('settings.timeline.unread_notification.description') }}
|
||||
</p>
|
||||
|
||||
<el-form-item for="direct" :label="$t('settings.timeline.unread_notification.direct')">
|
||||
<el-switch v-model="directNotify" id="direct" />
|
||||
</el-form-item>
|
||||
<el-form-item for="local" :label="$t('settings.timeline.unread_notification.local')">
|
||||
<el-switch v-model="localNotify" id="local" />
|
||||
</el-form-item>
|
||||
<el-form-item for="public" :label="$t('settings.timeline.unread_notification.public')">
|
||||
<el-switch v-model="publicNotify" id="public" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-form class="use-marker section" size="default" label-position="right" label-width="250px">
|
||||
<h3>{{ $t('settings.timeline.use_marker.title') }}</h3>
|
||||
<el-form-item for="marker_home" :label="$t('settings.timeline.use_marker.home')">
|
||||
|
@ -41,39 +24,18 @@ export default defineComponent({
|
|||
const space = 'Settings/Timeline'
|
||||
const store = useStore()
|
||||
|
||||
const directNotify = computed({
|
||||
get: () => store.state.Settings.Timeline.setting.unreadNotification.direct,
|
||||
set: value =>
|
||||
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_UNREAD_NOTIFICATION}`, {
|
||||
direct: value
|
||||
})
|
||||
})
|
||||
const localNotify = computed({
|
||||
get: () => store.state.Settings.Timeline.setting.unreadNotification.local,
|
||||
set: value =>
|
||||
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_UNREAD_NOTIFICATION}`, {
|
||||
local: value
|
||||
})
|
||||
})
|
||||
const publicNotify = computed({
|
||||
get: () => store.state.Settings.Timeline.setting.unreadNotification.public,
|
||||
set: value =>
|
||||
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_UNREAD_NOTIFICATION}`, {
|
||||
public: value
|
||||
})
|
||||
})
|
||||
const marker_home = computed({
|
||||
get: () => store.state.Settings.Timeline.setting.useMarker.home,
|
||||
get: () => store.state.Settings.Timeline.setting.markerHome,
|
||||
set: value =>
|
||||
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_USER_MARKER}`, {
|
||||
home: value
|
||||
markerHome: value
|
||||
})
|
||||
})
|
||||
const marker_notifications = computed({
|
||||
get: () => store.state.Settings.Timeline.setting.useMarker.notifications,
|
||||
get: () => store.state.Settings.Timeline.setting.markerNotifications,
|
||||
set: value =>
|
||||
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_USER_MARKER}`, {
|
||||
notifications: value
|
||||
markerNotifications: value
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -82,9 +44,6 @@ export default defineComponent({
|
|||
})
|
||||
|
||||
return {
|
||||
directNotify,
|
||||
localNotify,
|
||||
publicNotify,
|
||||
marker_home,
|
||||
marker_notifications
|
||||
}
|
||||
|
|
|
@ -75,14 +75,10 @@ export default defineComponent({
|
|||
;(window as any).removeEventListener('dragleave', onDragLeave)
|
||||
;(window as any).removeEventListener('dragover', onDragOver)
|
||||
;(window as any).removeEventListener('drop', handleDrop)
|
||||
store.dispatch(`${space}/${ACTION_TYPES.STOP_STREAMINGS}`)
|
||||
store.dispatch(`${space}/${ACTION_TYPES.UNBIND_STREAMINGS}`)
|
||||
})
|
||||
|
||||
const clear = async () => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.UNBIND_STREAMINGS}`)
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.CLEAR_ACCOUNT}`)
|
||||
store.dispatch(`${space}/${ACTION_TYPES.CLEAR_CONTENTS_TIMELINES}`)
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.REMOVE_SHORTCUT_EVENTS}`)
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.CLEAR_UNREAD}`)
|
||||
return 'clear'
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
<template v-slot="{ item, index, active }">
|
||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||
<toot
|
||||
v-if="account.account && account.server"
|
||||
:message="item"
|
||||
:focused="item.uri === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="[]"
|
||||
:account="account.account"
|
||||
:server="account.server"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusRight="focusSidebar"
|
||||
|
@ -26,7 +29,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref, watch } from 'vue'
|
||||
import { computed, defineComponent, onMounted, ref, watch, reactive } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { useStore } from '@/store'
|
||||
|
@ -40,6 +43,9 @@ import { EventEmitter } from '@/components/event'
|
|||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Bookmarks'
|
||||
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'bookmarks',
|
||||
|
@ -54,22 +60,33 @@ export default defineComponent({
|
|||
const focusedId = ref<string | null>(null)
|
||||
const heading = ref<boolean>(true)
|
||||
const scroller = ref<any>()
|
||||
const lazyLoading = ref(false)
|
||||
const { j, k, Ctrl_r } = useMagicKeys()
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
|
||||
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
|
||||
account: null,
|
||||
server: null
|
||||
})
|
||||
|
||||
const bookmarks = computed(() => store.state.TimelineSpace.Contents.Bookmarks.bookmarks)
|
||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.Bookmarks.lazyLoading)
|
||||
const account = computed(() => store.state.TimelineSpace.account)
|
||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||
const currentFocusedIndex = computed(() => bookmarks.value.findIndex(toot => focusedId.value === toot.uri))
|
||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
account.account = a
|
||||
account.server = s
|
||||
|
||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||
store.commit(`TimelineSpace/Contents/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.FETCH_BOOKMARKS}`, account.value)
|
||||
.dispatch(`${space}/${ACTION_TYPES.FETCH_BOOKMARKS}`, account)
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.bookmark_fetch_error'),
|
||||
|
@ -108,12 +125,18 @@ export default defineComponent({
|
|||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||
!lazyLoading.value
|
||||
) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_BOOKMARKS}`, bookmarks.value[bookmarks.value.length - 1]).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.bookmark_fetch_error'),
|
||||
type: 'error'
|
||||
lazyLoading.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_BOOKMARKS}`, account)
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.bookmark_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
lazyLoading.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
// for upper
|
||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||
|
@ -126,7 +149,7 @@ export default defineComponent({
|
|||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||
try {
|
||||
await reloadable()
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_BOOKMARKS}`, account.value).catch(() => {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_BOOKMARKS}`, account).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.bookmark_fetch_error'),
|
||||
type: 'error'
|
||||
|
@ -178,7 +201,8 @@ export default defineComponent({
|
|||
focusToot,
|
||||
openSideBar,
|
||||
heading,
|
||||
upper
|
||||
upper,
|
||||
account
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<template>
|
||||
<div id="directmessages">
|
||||
<div></div>
|
||||
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||
<template v-slot="{ item, index, active }">
|
||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||
<toot
|
||||
v-if="account.account && account.server"
|
||||
:message="item"
|
||||
:focused="item.uri + item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="[]"
|
||||
:account="account.account"
|
||||
:server="account.server"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusRight="focusSidebar"
|
||||
|
@ -27,7 +29,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, onMounted, onBeforeUpdate, onBeforeUnmount, onUnmounted, watch } from 'vue'
|
||||
import { defineComponent, ref, computed, onMounted, onBeforeUpdate, onBeforeUnmount, watch, reactive } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { useStore } from '@/store'
|
||||
|
@ -35,14 +37,13 @@ import { useI18next } from 'vue3-i18next'
|
|||
import { useRoute } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Entity } from 'megalodon'
|
||||
import useReloadable from '@/components/utils/reloadable'
|
||||
import Toot from '@/components/organisms/Toot.vue'
|
||||
import { EventEmitter } from '@/components/event'
|
||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/DirectMessages'
|
||||
import { MUTATION_TYPES as SIDE_MENU_MUTATION } from '@/store/TimelineSpace/SideMenu'
|
||||
import { MUTATION_TYPES as TIMELINE_MUTATION, ACTION_TYPES as TIMELINE_ACTION } from '@/store/TimelineSpace'
|
||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||
import { ACTION_TYPES as CONTENTS_ACTION } from '@/store/TimelineSpace/Contents'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'directmessages',
|
||||
|
@ -52,31 +53,33 @@ export default defineComponent({
|
|||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const i18n = useI18next()
|
||||
const { reloadable } = useReloadable(store, route, i18n)
|
||||
const { j, k, Ctrl_r } = useMagicKeys()
|
||||
const { j, k } = useMagicKeys()
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
|
||||
const focusedId = ref<string | null>(null)
|
||||
const scroller = ref<any>()
|
||||
const lazyLoading = ref(false)
|
||||
const heading = ref(true)
|
||||
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
|
||||
account: null,
|
||||
server: null
|
||||
})
|
||||
|
||||
const timeline = computed(() => store.state.TimelineSpace.Contents.DirectMessages.timeline)
|
||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.DirectMessages.lazyLoading)
|
||||
const heading = computed(() => store.state.TimelineSpace.Contents.DirectMessages.heading)
|
||||
const timeline = computed(() => store.state.TimelineSpace.Contents.DirectMessages.timeline[id.value])
|
||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||
const unreadNotification = computed(() => store.state.TimelineSpace.timelineSetting.unreadNotification)
|
||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||
|
||||
onMounted(async () => {
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
account.account = a
|
||||
account.server = s
|
||||
|
||||
store.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_HOME_TIMELINE}`, false)
|
||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||
if (!unreadNotification.value.direct) {
|
||||
store.commit(`TimelineSpace/Contents/${CONTENTS_ACTION.CHANGE_LOADING}`, true)
|
||||
await initialize().finally(() => {
|
||||
store.commit(`TimelineSpace/Contents/${CONTENTS_ACTION.CHANGE_LOADING}`, false)
|
||||
})
|
||||
}
|
||||
})
|
||||
onBeforeUpdate(() => {
|
||||
if (store.state.TimelineSpace.SideMenu.unreadDirectMessagesTimeline && heading.value) {
|
||||
|
@ -84,30 +87,13 @@ export default defineComponent({
|
|||
}
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
if (!unreadNotification.value.direct) {
|
||||
store.dispatch(`TimelineSpace/${TIMELINE_ACTION.STOP_DIRECT_MESSAGES_STREAMING}`)
|
||||
store.dispatch(`TimelineSpace/${TIMELINE_ACTION.UNBIND_DIRECT_MESSAGES_STREAMING}`)
|
||||
}
|
||||
})
|
||||
onUnmounted(() => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_TIMELINE}`)
|
||||
if (!unreadNotification.value.direct) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CLEAR_TIMELINE}`)
|
||||
}
|
||||
})
|
||||
watch(startReload, (newVal, oldVal) => {
|
||||
if (!oldVal && newVal) {
|
||||
reload().finally(() => {
|
||||
store.commit(`TimelineSpace/HeaderMenu/${HEADER_MUTATION.CHANGE_RELOAD}`, false)
|
||||
})
|
||||
}
|
||||
EventEmitter.off('focus-timeline')
|
||||
})
|
||||
watch(focusedId, (newVal, _oldVal) => {
|
||||
if (newVal && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if (newVal === null && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
heading.value = true
|
||||
}
|
||||
})
|
||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||
|
@ -120,20 +106,7 @@ export default defineComponent({
|
|||
whenever(logicAnd(k, shortcutEnabled), () => {
|
||||
focusPrev()
|
||||
})
|
||||
whenever(logicAnd(Ctrl_r, shortcutEnabled), () => {
|
||||
reload()
|
||||
})
|
||||
|
||||
const initialize = async () => {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_TIMELINE}`).catch(_ => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
await store.dispatch(`TimelineSpace/${TIMELINE_ACTION.BIND_DIRECT_MESSAGES_STREAMING}`)
|
||||
store.dispatch(`TimelineSpace/${TIMELINE_ACTION.START_DIRECT_MESSAGES_STREAMING}`)
|
||||
}
|
||||
const onScroll = (event: Event) => {
|
||||
// for lazyLoading
|
||||
if (
|
||||
|
@ -141,32 +114,38 @@ export default defineComponent({
|
|||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||
!lazyLoading.value
|
||||
) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, timeline.value[timeline.value.length - 1]).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
lazyLoading.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||
statuses: timeline.value[timeline.value.length - 1],
|
||||
account: account.account,
|
||||
server: account.server
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
lazyLoading.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
heading.value = true
|
||||
}
|
||||
}
|
||||
const updateToot = (message: Entity.Status) => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, message)
|
||||
if (account.account) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, { status: message, accountId: account.account.id })
|
||||
}
|
||||
}
|
||||
const deleteToot = (message: Entity.Status) => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.DELETE_TOOT}`, message.id)
|
||||
}
|
||||
const reload = async () => {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||
try {
|
||||
await reloadable()
|
||||
} finally {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, false)
|
||||
if (account.account) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.DELETE_TOOT}`, { statusId: message.id, accountId: account.account.id })
|
||||
}
|
||||
}
|
||||
const upper = () => {
|
||||
|
@ -205,7 +184,8 @@ export default defineComponent({
|
|||
focusToot,
|
||||
openSideBar,
|
||||
heading,
|
||||
upper
|
||||
upper,
|
||||
account
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -5,10 +5,13 @@
|
|||
<template v-slot="{ item, index, active }">
|
||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||
<toot
|
||||
v-if="account.account && account.server"
|
||||
:message="item"
|
||||
:focused="item.uri === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="[]"
|
||||
:account="account.account"
|
||||
:server="account.server"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusRight="focusSidebar"
|
||||
|
@ -27,7 +30,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { defineComponent, computed, ref, onMounted, onUnmounted, watch, reactive } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { useStore } from '@/store'
|
||||
|
@ -40,6 +43,10 @@ import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Fav
|
|||
import { MUTATION_TYPES as CONTENTS_MUTATION } from '@/store/TimelineSpace/Contents'
|
||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'favourites',
|
||||
|
@ -47,6 +54,7 @@ export default defineComponent({
|
|||
setup() {
|
||||
const space = 'TimelineSpace/Contents/Favourites'
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const i18n = useI18next()
|
||||
|
||||
const heading = ref<boolean>(false)
|
||||
|
@ -54,21 +62,32 @@ export default defineComponent({
|
|||
const scroller = ref<any>()
|
||||
const { j, k, Ctrl_r } = useMagicKeys()
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
|
||||
const lazyLoading = ref(false)
|
||||
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
|
||||
account: null,
|
||||
server: null
|
||||
})
|
||||
|
||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||
const account = computed(() => store.state.TimelineSpace.account)
|
||||
const favourites = computed(() => store.state.TimelineSpace.Contents.Favourites.favourites)
|
||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.Favourites.lazyLoading)
|
||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||
const currentFocusedIndex = computed(() => favourites.value.findIndex(status => focusedId.value === status.uri))
|
||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
account.account = a
|
||||
account.server = s
|
||||
|
||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||
|
||||
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true)
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.FETCH_FAVOURITES}`, account.value)
|
||||
.dispatch(`${space}/${ACTION_TYPES.FETCH_FAVOURITES}`, account)
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.favourite_fetch_error'),
|
||||
|
@ -116,12 +135,18 @@ export default defineComponent({
|
|||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||
!lazyLoading.value
|
||||
) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_FAVOURITES}`, favourites.value[favourites.value.length - 1]).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.favourite_fetch_error'),
|
||||
type: 'error'
|
||||
lazyLoading.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_FAVOURITES}`, account)
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.favourite_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
lazyLoading.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
// for upper
|
||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||
|
@ -139,7 +164,7 @@ export default defineComponent({
|
|||
const reload = async () => {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||
try {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_FAVOURITES}`, account.value).catch(() => {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_FAVOURITES}`, account).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.favourite_fetch_error'),
|
||||
type: 'error'
|
||||
|
@ -185,7 +210,8 @@ export default defineComponent({
|
|||
focusToot,
|
||||
openSideBar,
|
||||
heading,
|
||||
upper
|
||||
upper,
|
||||
account
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -7,13 +7,17 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, onMounted } from 'vue'
|
||||
import { defineComponent, computed, onMounted, reactive } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useI18next } from 'vue3-i18next'
|
||||
import { Entity } from 'megalodon'
|
||||
import { useStore } from '@/store'
|
||||
import User from '@/components/molecules/User.vue'
|
||||
import { ACTION_TYPES } from '@/store/TimelineSpace/Contents/FollowRequests'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'follow-requests',
|
||||
|
@ -21,8 +25,17 @@ export default defineComponent({
|
|||
setup() {
|
||||
const space = 'TimelineSpace/Contents/FollowRequests'
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const i18n = useI18next()
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
|
||||
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
|
||||
account: null,
|
||||
server: null
|
||||
})
|
||||
|
||||
const requests = computed(() => store.state.TimelineSpace.Contents.FollowRequests.requests)
|
||||
|
||||
onMounted(async () => {
|
||||
|
@ -30,23 +43,27 @@ export default defineComponent({
|
|||
})
|
||||
|
||||
const initialize = async () => {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_REQUESTS}`).catch(_ => {
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
account.account = a
|
||||
account.server = s
|
||||
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_REQUESTS}`, account).catch(_ => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
}
|
||||
const accept = (account: Entity.Account) => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.ACCEPT_REQUEST}`, account).catch(_ => {
|
||||
const accept = (user: Entity.Account) => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.ACCEPT_REQUEST}`, { user, account: account.account, server: account.server }).catch(_ => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.follow_request_accept_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
}
|
||||
const reject = (account: Entity.Account) => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.REJECT_REQUEST}`, account).catch(_ => {
|
||||
const reject = (user: Entity.Account) => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.REJECT_REQUEST}`, { user, account: account.account, server: account.server }).catch(_ => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.follow_request_reject_error'),
|
||||
type: 'error'
|
||||
|
|
|
@ -11,11 +11,6 @@
|
|||
<div class="form-item input">
|
||||
<input v-model="tag" :placeholder="$t('hashtag.tag_name')" class="search-keyword" v-on:keyup.enter="search" autofocus />
|
||||
</div>
|
||||
<div class="form-item" v-show="tagPage">
|
||||
<el-button link :title="$t('hashtag.save_tag')" @click="save">
|
||||
<font-awesome-icon icon="thumbtack" />
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
|
@ -26,14 +21,12 @@
|
|||
<script lang="ts">
|
||||
import { computed, defineComponent, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useStore } from '@/store'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'hashtag',
|
||||
setup() {
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
|
||||
const tag = ref<string>('')
|
||||
const id = computed(() => route.params.id)
|
||||
|
@ -58,16 +51,12 @@ export default defineComponent({
|
|||
const back = () => {
|
||||
router.push({ path: `/${id.value}/hashtag` })
|
||||
}
|
||||
const save = () => {
|
||||
store.dispatch('TimelineSpace/Contents/Hashtag/saveTag', tag.value)
|
||||
}
|
||||
|
||||
return {
|
||||
tagPage,
|
||||
tag,
|
||||
back,
|
||||
search,
|
||||
save
|
||||
search
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<template>
|
||||
<div name="tag" class="tag-timeline">
|
||||
<div class="unread">{{ unreads.length > 0 ? unreads.length : '' }}</div>
|
||||
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||
<template v-slot="{ item, index, active }">
|
||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||
<toot
|
||||
v-if="account.account && account.server"
|
||||
:message="item"
|
||||
:focused="item.uri + item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="[]"
|
||||
:account="account.account"
|
||||
:server="account.server"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusRight="focusSidebar"
|
||||
|
@ -27,7 +29,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onBeforeUnmount, onMounted, ref, toRefs, watch } from 'vue'
|
||||
import { computed, defineComponent, onBeforeUnmount, onMounted, ref, toRefs, watch, reactive } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
@ -42,6 +44,9 @@ import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
|||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||
import { MUTATION_TYPES as CONTENTS_MUTATION } from '@/store/TimelineSpace/Contents'
|
||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Hashtag/Tag'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'tag',
|
||||
|
@ -55,21 +60,31 @@ export default defineComponent({
|
|||
const { reloadable } = useReloadable(store, route, i18n)
|
||||
const { j, k, Ctrl_r } = useMagicKeys()
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
|
||||
const { tag } = toRefs(props)
|
||||
const focusedId = ref<string | null>(null)
|
||||
const scroller = ref<any>(null)
|
||||
const lazyLoading = ref(false)
|
||||
const heading = ref(true)
|
||||
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
|
||||
account: null,
|
||||
server: null
|
||||
})
|
||||
|
||||
const timeline = computed(() => store.state.TimelineSpace.Contents.Hashtag.Tag.timeline)
|
||||
const unreads = computed(() => store.state.TimelineSpace.Contents.Hashtag.Tag.unreads)
|
||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.Hashtag.Tag.lazyLoading)
|
||||
const heading = computed(() => store.state.TimelineSpace.Contents.Hashtag.Tag.heading)
|
||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
account.account = a
|
||||
account.server = s
|
||||
|
||||
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true)
|
||||
load(tag.value).finally(() => {
|
||||
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, false)
|
||||
|
@ -92,9 +107,9 @@ export default defineComponent({
|
|||
})
|
||||
watch(focusedId, (newVal, _oldVal) => {
|
||||
if (newVal && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if (newVal === null && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
heading.value = true
|
||||
}
|
||||
})
|
||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||
|
@ -118,13 +133,13 @@ export default defineComponent({
|
|||
})
|
||||
|
||||
const load = async (tag: string) => {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH}`, tag).catch(() => {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH}`, { tag: tag, account: account.account, server: account.server }).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
store.dispatch(`${space}/${ACTION_TYPES.START_STREAMING}`, tag).catch(() => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.START_STREAMING}`, { tag: tag, account: account.account }).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.start_streaming_error'),
|
||||
type: 'error'
|
||||
|
@ -133,9 +148,7 @@ export default defineComponent({
|
|||
return true
|
||||
}
|
||||
const reset = () => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_TIMELINE}`)
|
||||
store.commit(`${space}/${MUTATION_TYPES.CLEAR_TIMELINE}`)
|
||||
heading.value = true
|
||||
const el = document.getElementById('scroller')
|
||||
if (el !== undefined && el !== null) {
|
||||
el.removeEventListener('scroll', onScroll)
|
||||
|
@ -148,10 +161,13 @@ export default defineComponent({
|
|||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||
!lazyLoading.value
|
||||
) {
|
||||
lazyLoading.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||
tag: tag.value,
|
||||
status: timeline.value[timeline.value.length - 1]
|
||||
status: timeline.value[timeline.value.length - 1],
|
||||
account: account.account,
|
||||
server: account.server
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
|
@ -159,13 +175,15 @@ export default defineComponent({
|
|||
type: 'error'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
lazyLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.MERGE_UNREADS}`)
|
||||
heading.value = true
|
||||
}
|
||||
}
|
||||
const updateToot = (toot: Entity.Status) => {
|
||||
|
@ -232,7 +250,7 @@ export default defineComponent({
|
|||
openSideBar,
|
||||
heading,
|
||||
upper,
|
||||
unreads
|
||||
account
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div id="home">
|
||||
<div class="unread">{{ unreads.length > 0 ? unreads.length : '' }}</div>
|
||||
<DynamicScroller :items="filteredTimeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||
<template v-slot="{ item, index, active }">
|
||||
<template v-if="item.id === 'loading-card'">
|
||||
|
@ -11,10 +10,13 @@
|
|||
<template v-else>
|
||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||
<toot
|
||||
v-if="account.account && account.server"
|
||||
:message="item"
|
||||
:focused="item.uri + item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
:account="account.account"
|
||||
:server="account.server"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusRight="focusSidebar"
|
||||
|
@ -35,7 +37,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, onMounted, onBeforeUpdate, watch, onUnmounted } from 'vue'
|
||||
import { defineComponent, ref, computed, onMounted, onBeforeUpdate, watch, onUnmounted, reactive } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
@ -48,9 +50,9 @@ import StatusLoading from '@/components/organisms/StatusLoading.vue'
|
|||
import { EventEmitter } from '@/components/event'
|
||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Home'
|
||||
import { MUTATION_TYPES as SIDE_MENU_MUTATION } from '@/store/TimelineSpace/SideMenu'
|
||||
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||
import useReloadable from '@/components/utils/reloadable'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'home',
|
||||
|
@ -60,26 +62,35 @@ export default defineComponent({
|
|||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const i18n = useI18next()
|
||||
const { reloadable } = useReloadable(store, route, i18n)
|
||||
const { j, k, Ctrl_r } = useMagicKeys()
|
||||
const { j, k } = useMagicKeys()
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
|
||||
const focusedId = ref<string | null>(null)
|
||||
const loadingMore = ref(false)
|
||||
const scroller = ref<any>()
|
||||
const lazyLoading = ref(false)
|
||||
const heading = ref(true)
|
||||
const showReblogs = ref(true)
|
||||
const showReplies = ref(true)
|
||||
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
|
||||
account: null,
|
||||
server: null
|
||||
})
|
||||
|
||||
const timeline = computed(() => store.state.TimelineSpace.Contents.Home.timeline[id.value])
|
||||
|
||||
const timeline = computed(() => store.state.TimelineSpace.Contents.Home.timeline)
|
||||
const unreads = computed(() => store.state.TimelineSpace.Contents.Home.unreads)
|
||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.Home.lazyLoading)
|
||||
const heading = computed(() => store.state.TimelineSpace.Contents.Home.heading)
|
||||
const showReblogs = computed(() => store.state.TimelineSpace.Contents.Home.showReblogs)
|
||||
const showReplies = computed(() => store.state.TimelineSpace.Contents.Home.showReplies)
|
||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||
const filters = computed(() => store.getters[`${space}/filters`])
|
||||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||
const filteredTimeline = computed(() => {
|
||||
if (!timeline.value) {
|
||||
return []
|
||||
}
|
||||
return timeline.value.filter(toot => {
|
||||
if ('url' in toot) {
|
||||
if (toot.in_reply_to_id) {
|
||||
|
@ -95,13 +106,13 @@ export default defineComponent({
|
|||
})
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
account.account = a
|
||||
account.server = s
|
||||
|
||||
store.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_HOME_TIMELINE}`, false)
|
||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||
|
||||
if (heading.value && timeline.value.length > 0) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`)
|
||||
}
|
||||
})
|
||||
onBeforeUpdate(() => {
|
||||
if (store.state.TimelineSpace.SideMenu.unreadHomeTimeline && heading.value) {
|
||||
|
@ -109,35 +120,26 @@ export default defineComponent({
|
|||
}
|
||||
})
|
||||
onUnmounted(() => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_TIMELINE}`)
|
||||
const el = document.getElementById('scroller')
|
||||
if (el !== undefined && el !== null) {
|
||||
el.removeEventListener('scroll', onScroll)
|
||||
el.scrollTop = 0
|
||||
}
|
||||
})
|
||||
watch(startReload, (newVal, oldVal) => {
|
||||
if (!oldVal && newVal) {
|
||||
reload().finally(() => {
|
||||
store.commit(`TimelineSpace/HeaderMenu/${HEADER_MUTATION.CHANGE_RELOAD}`, false)
|
||||
})
|
||||
}
|
||||
})
|
||||
watch(
|
||||
timeline,
|
||||
(newState, _oldState) => {
|
||||
if (heading.value && newState.length > 0) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`)
|
||||
if (heading.value && newState.length > 0 && account.account && account.server) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`, account)
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
watch(focusedId, (newVal, _oldVal) => {
|
||||
if (newVal && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if (newVal === null && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
heading.value = true
|
||||
}
|
||||
})
|
||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||
|
@ -150,9 +152,6 @@ export default defineComponent({
|
|||
whenever(logicAnd(k, shortcutEnabled), () => {
|
||||
focusPrev()
|
||||
})
|
||||
whenever(logicAnd(Ctrl_r, shortcutEnabled), () => {
|
||||
reload()
|
||||
})
|
||||
|
||||
const onScroll = (event: Event) => {
|
||||
// for lazyLoading
|
||||
|
@ -161,42 +160,49 @@ export default defineComponent({
|
|||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||
!lazyLoading.value
|
||||
) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, timeline.value[timeline.value.length - 1]).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
lazyLoading.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||
lastStatus: timeline.value[timeline.value.length - 1],
|
||||
account: account.account,
|
||||
server: account.server
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
lazyLoading.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if ((event.target as HTMLElement)!.scrollTop <= 5 && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.MERGE_UNREADS}`)
|
||||
heading.value = true
|
||||
}
|
||||
}
|
||||
const updateToot = (message: Entity.Status) => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, message)
|
||||
if (account.account) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, { status: message, accountId: account.account.id })
|
||||
}
|
||||
}
|
||||
const deleteToot = (message: Entity.Status) => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.DELETE_TOOT}`, message.id)
|
||||
if (account.account) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.DELETE_TOOT}`, { statusId: message.id, accountId: account.account.id })
|
||||
}
|
||||
}
|
||||
const fetchTimelineSince = (since_id: string) => {
|
||||
loadingMore.value = true
|
||||
store.dispatch(`${space}/${ACTION_TYPES.FETCH_TIMELINE_SINCE}`, since_id).finally(() => {
|
||||
setTimeout(() => {
|
||||
loadingMore.value = false
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
const reload = async () => {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||
try {
|
||||
await reloadable()
|
||||
} finally {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, false)
|
||||
}
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.FETCH_TIMELINE_SINCE}`, { sinceId: since_id, account: account.account, server: account.server })
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
loadingMore.value = false
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
const upper = () => {
|
||||
scroller.value.scrollToItem(0)
|
||||
|
@ -240,7 +246,7 @@ export default defineComponent({
|
|||
openSideBar,
|
||||
heading,
|
||||
upper,
|
||||
unreads
|
||||
account
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<template>
|
||||
<div name="list" class="list-timeline">
|
||||
<div class="unread">{{ unreads.length > 0 ? unreads.length : '' }}</div>
|
||||
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||
<template v-slot="{ item, index, active }">
|
||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||
<toot
|
||||
v-if="account.account && account.server"
|
||||
:message="item"
|
||||
:focused="item.uri + item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="[]"
|
||||
:account="account.account"
|
||||
:server="account.server"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusRight="focusSidebar"
|
||||
|
@ -27,7 +29,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, ref, computed, onMounted, watch, onBeforeUnmount, onUnmounted } from 'vue'
|
||||
import { defineComponent, toRefs, ref, computed, onMounted, watch, onBeforeUnmount, onUnmounted, reactive } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
@ -40,6 +42,10 @@ import { MUTATION_TYPES as CONTENTS_MUTATION } from '@/store/TimelineSpace/Conte
|
|||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Lists/Show'
|
||||
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'list',
|
||||
|
@ -48,24 +54,35 @@ export default defineComponent({
|
|||
setup(props) {
|
||||
const space = 'TimelineSpace/Contents/Lists/Show'
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const i18n = useI18next()
|
||||
const { j, k, Ctrl_r } = useMagicKeys()
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
|
||||
const { list_id } = toRefs(props)
|
||||
const focusedId = ref<string | null>(null)
|
||||
const scroller = ref<any>(null)
|
||||
const lazyLoading = ref<boolean>(false)
|
||||
const heading = ref<boolean>(true)
|
||||
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
|
||||
account: null,
|
||||
server: null
|
||||
})
|
||||
|
||||
const timeline = computed(() => store.state.TimelineSpace.Contents.Lists.Show.timeline)
|
||||
const unreads = computed(() => store.state.TimelineSpace.Contents.Lists.Show.unreads)
|
||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.Lists.Show.lazyLoading)
|
||||
const heading = computed(() => store.state.TimelineSpace.Contents.Lists.Show.heading)
|
||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
account.account = a
|
||||
account.server = s
|
||||
|
||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true)
|
||||
load().finally(() => {
|
||||
|
@ -87,9 +104,9 @@ export default defineComponent({
|
|||
})
|
||||
watch(focusedId, (newVal, _oldVal) => {
|
||||
if (newVal && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if (newVal === null && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
heading.value = true
|
||||
}
|
||||
})
|
||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||
|
@ -110,9 +127,7 @@ export default defineComponent({
|
|||
store.dispatch(`${space}/${ACTION_TYPES.STOP_STREAMING}`)
|
||||
})
|
||||
onUnmounted(() => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_TIMELINE}`)
|
||||
store.commit(`${space}/${MUTATION_TYPES.CLEAR_TIMELINE}`)
|
||||
heading.value = true
|
||||
const el = document.getElementById('scroller')
|
||||
if (el !== undefined && el !== null) {
|
||||
el.removeEventListener('scroll', onScroll)
|
||||
|
@ -123,14 +138,18 @@ export default defineComponent({
|
|||
const load = async () => {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.STOP_STREAMING}`)
|
||||
try {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_TIMELINE}`, list_id.value)
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_TIMELINE}`, {
|
||||
listID: list_id.value,
|
||||
account: account.account,
|
||||
server: account.server
|
||||
})
|
||||
} catch (err) {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
}
|
||||
store.dispatch(`${space}/${ACTION_TYPES.START_STREAMING}`, list_id.value).catch(() => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.START_STREAMING}`, { listID: list_id.value, account: account.account }).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.start_streaming_error'),
|
||||
type: 'error'
|
||||
|
@ -144,10 +163,13 @@ export default defineComponent({
|
|||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||
!lazyLoading
|
||||
) {
|
||||
lazyLoading.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||
list_id: list_id.value,
|
||||
status: timeline.value[timeline.value.length - 1]
|
||||
status: timeline.value[timeline.value.length - 1],
|
||||
account: account.account,
|
||||
server: account.server
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
|
@ -155,13 +177,15 @@ export default defineComponent({
|
|||
type: 'error'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
lazyLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.MERGE_UNREADS}`)
|
||||
heading.value = true
|
||||
}
|
||||
}
|
||||
const reload = async () => {
|
||||
|
@ -227,7 +251,7 @@ export default defineComponent({
|
|||
openSideBar,
|
||||
heading,
|
||||
upper,
|
||||
unreads
|
||||
account
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<template>
|
||||
<div id="local">
|
||||
<div class="unread">{{ unreads.length > 0 ? unreads.length : '' }}</div>
|
||||
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||
<template v-slot="{ item, index, active }">
|
||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||
<toot
|
||||
v-if="account.account && account.server"
|
||||
:message="item"
|
||||
:focused="item.uri + item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="[]"
|
||||
:account="account.account"
|
||||
:server="account.server"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusRight="focusSidebar"
|
||||
|
@ -27,7 +29,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { computed, defineComponent, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, ref, watch, reactive } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
@ -37,12 +39,11 @@ import { useRoute } from 'vue-router'
|
|||
import { useStore } from '@/store'
|
||||
import Toot from '@/components/organisms/Toot.vue'
|
||||
import { EventEmitter } from '@/components/event'
|
||||
import useReloadable from '@/components/utils/reloadable'
|
||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Local'
|
||||
import { MUTATION_TYPES as SIDE_MENU_MUTATION } from '@/store/TimelineSpace/SideMenu'
|
||||
import { MUTATION_TYPES as TIMELINE_MUTATION, ACTION_TYPES as TIMELINE_ACTION } from '@/store/TimelineSpace'
|
||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||
import { MUTATION_TYPES as CONTENTS_MUTATION } from '@/store/TimelineSpace/Contents'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'local',
|
||||
|
@ -52,32 +53,34 @@ export default defineComponent({
|
|||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const i18n = useI18next()
|
||||
const { reloadable } = useReloadable(store, route, i18n)
|
||||
const { j, k, Ctrl_r } = useMagicKeys()
|
||||
const { j, k } = useMagicKeys()
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
|
||||
const focusedId = ref<string | null>(null)
|
||||
const scroller = ref<any>(null)
|
||||
const lazyLoading = ref(false)
|
||||
const heading = ref(true)
|
||||
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
|
||||
account: null,
|
||||
server: null
|
||||
})
|
||||
|
||||
const timeline = computed(() => store.state.TimelineSpace.Contents.Local.timeline[id.value])
|
||||
|
||||
const timeline = computed(() => store.state.TimelineSpace.Contents.Local.timeline)
|
||||
const unreads = computed(() => store.state.TimelineSpace.Contents.Local.unreads)
|
||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.Local.lazyLoading)
|
||||
const heading = computed(() => store.state.TimelineSpace.Contents.Local.heading)
|
||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||
const unreadNotification = computed(() => store.state.TimelineSpace.timelineSetting.unreadNotification)
|
||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||
|
||||
onMounted(async () => {
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
account.account = a
|
||||
account.server = s
|
||||
|
||||
store.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_LOCAL_TIMELINE}`, false)
|
||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||
if (!unreadNotification.value.local) {
|
||||
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true)
|
||||
await initialize().finally(() => {
|
||||
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, false)
|
||||
})
|
||||
}
|
||||
})
|
||||
onBeforeUpdate(() => {
|
||||
if (store.state.TimelineSpace.SideMenu.unreadLocalTimeline && heading.value) {
|
||||
|
@ -85,37 +88,21 @@ export default defineComponent({
|
|||
}
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
if (!unreadNotification.value.local) {
|
||||
store.dispatch(`TimelineSpace/${TIMELINE_ACTION.STOP_LOCAL_STREAMING}`)
|
||||
store.dispatch(`TimelineSpace/${TIMELINE_ACTION.UNBIND_LOCAL_STREAMING}`)
|
||||
}
|
||||
EventEmitter.off('focus-timeline')
|
||||
})
|
||||
onUnmounted(() => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_TIMELINE}`)
|
||||
if (!unreadNotification.value.local) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CLEAR_TIMELINE}`)
|
||||
}
|
||||
const el = document.getElementById('scroller')
|
||||
if (el) {
|
||||
el.removeEventListener('scroll', onScroll)
|
||||
el.scrollTop = 0
|
||||
}
|
||||
})
|
||||
watch(startReload, (newVal, oldVal) => {
|
||||
if (!oldVal && newVal) {
|
||||
reload().finally(() => {
|
||||
store.commit(`TimelineSpace/HeaderMenu/${HEADER_MUTATION.CHANGE_RELOAD}`, false)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
watch(focusedId, (newVal, _oldVal) => {
|
||||
if (newVal && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if (newVal === null && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
heading.value = true
|
||||
}
|
||||
})
|
||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||
|
@ -128,53 +115,46 @@ export default defineComponent({
|
|||
whenever(logicAnd(k, shortcutEnabled), () => {
|
||||
focusPrev()
|
||||
})
|
||||
whenever(logicAnd(Ctrl_r, shortcutEnabled), () => {
|
||||
reload()
|
||||
})
|
||||
|
||||
const initialize = async () => {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_LOCAL_TIMELINE}`).catch(_ => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
await store.dispatch(`TimelineSpace/${TIMELINE_ACTION.BIND_LOCAL_STREAMING}`)
|
||||
store.dispatch(`TimelineSpace/${TIMELINE_ACTION.START_LOCAL_STREAMING}`)
|
||||
}
|
||||
const onScroll = (event: Event) => {
|
||||
if (
|
||||
(event.target as HTMLElement)!.clientHeight + (event.target as HTMLElement)!.scrollTop >=
|
||||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||
!lazyLoading.value
|
||||
) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, timeline.value[timeline.value.length - 1]).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
lazyLoading.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||
lastStatus: timeline.value[timeline.value.length - 1],
|
||||
account: account.account,
|
||||
server: account.server
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
lazyLoading.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.MERGE_UNREADS}`)
|
||||
}
|
||||
}
|
||||
const reload = async () => {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||
try {
|
||||
await reloadable()
|
||||
} finally {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, false)
|
||||
heading.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const updateToot = (message: Entity.Status) => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, message)
|
||||
if (account.account) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, { status: message, accountId: account.account.id })
|
||||
}
|
||||
}
|
||||
const deleteToot = (message: Entity.Status) => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.DELETE_TOOT}`, message.id)
|
||||
if (account.account) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.DELETE_TOOT}`, { statusId: message.id, accountId: account.account.id })
|
||||
}
|
||||
}
|
||||
const upper = () => {
|
||||
scroller.value.scrollToItem(0)
|
||||
|
@ -213,7 +193,7 @@ export default defineComponent({
|
|||
openSideBar,
|
||||
heading,
|
||||
upper,
|
||||
unreads
|
||||
account
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,226 +0,0 @@
|
|||
<template>
|
||||
<div id="mentions">
|
||||
<div></div>
|
||||
<DynamicScroller :items="mentions" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||
<template v-slot="{ item, index, active }">
|
||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.url]" :data-index="index" :watch-data="true">
|
||||
<notification
|
||||
:message="item"
|
||||
:focused="item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="[]"
|
||||
@update="updateToot"
|
||||
@focus-right="focusSidebar"
|
||||
@select-notification="focusNotification(item)"
|
||||
>
|
||||
</notification>
|
||||
</DynamicScrollerItem>
|
||||
</template>
|
||||
</DynamicScroller>
|
||||
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
|
||||
<el-button type="primary" @click="upper" circle>
|
||||
<font-awesome-icon icon="angle-up" class="upper-icon" />
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onBeforeUpdate, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { useI18next } from 'vue3-i18next'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { Entity } from 'megalodon'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useStore } from '@/store'
|
||||
import Notification from '@/components/organisms/Notification.vue'
|
||||
import StatusLoading from '@/components/organisms/StatusLoading.vue'
|
||||
import { EventEmitter } from '@/components/event'
|
||||
import useReloadable from '@/components/utils/reloadable'
|
||||
import { LoadingCard } from '@/types/loading-card'
|
||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Mentions'
|
||||
import { MUTATION_TYPES as SIDE_MENU_MUTATION } from '@/store/TimelineSpace/SideMenu'
|
||||
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'mentions',
|
||||
components: { Notification, StatusLoading },
|
||||
setup() {
|
||||
const space = 'TimelineSpace/Contents/Mentions'
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const i18n = useI18next()
|
||||
const { reloadable } = useReloadable(store, route, i18n)
|
||||
const { j, k, Ctrl_r } = useMagicKeys()
|
||||
|
||||
const focusedId = ref<string | null>(null)
|
||||
const scroller = ref<any>()
|
||||
|
||||
const mentions = computed<Array<Entity.Notification | LoadingCard>>(() => store.getters[`${space}/mentions`])
|
||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.Mentions.lazyLoading)
|
||||
const heading = computed(() => store.state.TimelineSpace.Contents.Mentions.heading)
|
||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||
const currentFocusedIndex = computed(() => mentions.value.findIndex(notification => focusedId.value === notification.id))
|
||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||
|
||||
onMounted(() => {
|
||||
store.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_MENTIONS}`, false)
|
||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||
})
|
||||
onBeforeUpdate(() => {
|
||||
if (store.state.TimelineSpace.SideMenu.unreadMentions && heading.value) {
|
||||
store.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_MENTIONS}`, false)
|
||||
}
|
||||
})
|
||||
onUnmounted(() => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_MENTIONS}`)
|
||||
const el = document.getElementById('scroller')
|
||||
if (el !== undefined && el !== null) {
|
||||
el.removeEventListener('scroll', onScroll)
|
||||
el.scrollTop = 0
|
||||
}
|
||||
})
|
||||
watch(startReload, (newVal, oldVal) => {
|
||||
if (!oldVal && newVal) {
|
||||
reload().finally(() => {
|
||||
store.commit(`TimelineSpace/HeaderMenu/${HEADER_MUTATION.CHANGE_RELOAD}`, false)
|
||||
})
|
||||
}
|
||||
})
|
||||
watch(focusedId, (newVal, _oldVal) => {
|
||||
if (newVal && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
} else if (newVal === null && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
}
|
||||
})
|
||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||
if (focusedId.value === null) {
|
||||
focusedId.value = mentions.value[0].id
|
||||
} else {
|
||||
focusNext()
|
||||
}
|
||||
})
|
||||
whenever(logicAnd(k, shortcutEnabled), () => {
|
||||
focusPrev()
|
||||
})
|
||||
whenever(logicAnd(Ctrl_r, shortcutEnabled), () => {
|
||||
reload()
|
||||
})
|
||||
|
||||
const onScroll = (event: Event) => {
|
||||
// for lazyLoading
|
||||
if (
|
||||
(event.target as HTMLElement)!.clientHeight + (event.target as HTMLElement)!.scrollTop >=
|
||||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||
!lazyLoading.value
|
||||
) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_MENTIONS}`, mentions.value[mentions.value.length - 1]).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
}
|
||||
}
|
||||
const updateToot = (message: Entity.Status) => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, message)
|
||||
}
|
||||
const reload = async () => {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||
try {
|
||||
await reloadable()
|
||||
} finally {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, false)
|
||||
}
|
||||
}
|
||||
const upper = () => {
|
||||
scroller.value.scrollToItem(0)
|
||||
focusedId.value = null
|
||||
}
|
||||
const focusNext = () => {
|
||||
if (currentFocusedIndex.value === -1) {
|
||||
focusedId.value = mentions.value[0].id
|
||||
} else if (currentFocusedIndex.value < mentions.value.length) {
|
||||
focusedId.value = mentions.value[currentFocusedIndex.value + 1].id
|
||||
}
|
||||
}
|
||||
const focusPrev = () => {
|
||||
if (currentFocusedIndex.value === 0) {
|
||||
focusedId.value = null
|
||||
} else if (currentFocusedIndex.value > 0) {
|
||||
focusedId.value = mentions.value[currentFocusedIndex.value - 1].id
|
||||
}
|
||||
}
|
||||
const focusNotification = (message: Entity.Notification) => {
|
||||
focusedId.value = message.id
|
||||
}
|
||||
const focusSidebar = () => {
|
||||
EventEmitter.emit('focus-sidebar')
|
||||
}
|
||||
|
||||
return {
|
||||
mentions,
|
||||
scroller,
|
||||
focusedId,
|
||||
modalOpened,
|
||||
updateToot,
|
||||
focusSidebar,
|
||||
focusNotification,
|
||||
openSideBar,
|
||||
heading,
|
||||
upper
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#mentions {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
scroll-behavior: auto;
|
||||
|
||||
.scroller {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.loading-card {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.loading-card:empty {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.upper {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.upper-with-side-bar {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: calc(20px + var(--current-sidebar-width));
|
||||
transition: all 0.5s;
|
||||
}
|
||||
|
||||
.upper-icon {
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" src="@/assets/timeline-transition.scss"></style>
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<div id="notifications">
|
||||
<div></div>
|
||||
<DynamicScroller :items="handledNotifications" :min-item-size="20" id="scroller" class="scroller" ref="scroller">
|
||||
<DynamicScroller :items="notifications" :min-item-size="20" id="scroller" class="scroller" ref="scroller">
|
||||
<template v-slot="{ item, index, active }">
|
||||
<template v-if="item.id === 'loading-card'">
|
||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.id]" :data-index="index" :watchData="true">
|
||||
|
@ -11,10 +10,13 @@
|
|||
<template v-else>
|
||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.url]" :data-index="index" :watchData="true">
|
||||
<notification
|
||||
v-if="account.account && account.server"
|
||||
:message="item"
|
||||
:focused="item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
:account="account.account"
|
||||
:server="account.server"
|
||||
v-on:update="updateToot"
|
||||
@focusRight="focusSidebar"
|
||||
@selectNotification="focusNotification(item)"
|
||||
|
@ -33,7 +35,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, onMounted, onBeforeUpdate, onUnmounted, watch } from 'vue'
|
||||
import { defineComponent, ref, computed, onMounted, onBeforeUpdate, onUnmounted, watch, reactive } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
@ -44,11 +46,13 @@ import { EventEmitter } from '@/components/event'
|
|||
import { useStore } from '@/store'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useI18next } from 'vue3-i18next'
|
||||
import useReloadable from '@/components/utils/reloadable'
|
||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Notifications'
|
||||
import { MUTATION_TYPES as SIDE_MENU_MUTATION } from '@/store/TimelineSpace/SideMenu'
|
||||
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'notifications',
|
||||
|
@ -59,31 +63,40 @@ export default defineComponent({
|
|||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const i18n = useI18next()
|
||||
const { reloadable } = useReloadable(store, route, i18n)
|
||||
const { j, k, Ctrl_r } = useMagicKeys()
|
||||
const { j, k } = useMagicKeys()
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
|
||||
const focusedId = ref<string | null>(null)
|
||||
const loadingMore = ref(false)
|
||||
const scroller = ref<any>()
|
||||
const lazyLoading = ref(false)
|
||||
const heading = ref(true)
|
||||
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
|
||||
account: null,
|
||||
server: null
|
||||
})
|
||||
|
||||
const notifications = computed(() => store.state.TimelineSpace.Contents.Notifications.notifications)
|
||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.Notifications.lazyLoading)
|
||||
const heading = computed(() => store.state.TimelineSpace.Contents.Notifications.heading)
|
||||
const notifications = computed(() => store.state.TimelineSpace.Contents.Notifications.notifications[id.value])
|
||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||
const filters = computed(() => store.getters[`${space}/filters}`])
|
||||
const handledNotifications = computed(() => store.getters[`${space}/handledNotifications`])
|
||||
const currentFocusedIndex = computed(() => notifications.value.findIndex(notification => focusedId.value === notification.id))
|
||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
account.account = a
|
||||
account.server = s
|
||||
|
||||
store.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_NOTIFICATIONS}`, false)
|
||||
store.dispatch(`${space}/${ACTION_TYPES.RESET_BADGE}`)
|
||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||
|
||||
if (heading.value && handledNotifications.value.length > 0) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`)
|
||||
if (heading.value && notifications.value.length > 0) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`, account)
|
||||
}
|
||||
})
|
||||
onBeforeUpdate(() => {
|
||||
|
@ -92,41 +105,32 @@ export default defineComponent({
|
|||
}
|
||||
})
|
||||
onUnmounted(() => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_NOTIFICATIONS}`)
|
||||
const el = document.getElementById('scroller')
|
||||
if (el !== undefined && el !== null) {
|
||||
el.removeEventListener('scroll', onScroll)
|
||||
el.scrollTop = 0
|
||||
}
|
||||
})
|
||||
watch(startReload, (newVal, oldVal) => {
|
||||
if (!oldVal && newVal) {
|
||||
reload().finally(() => {
|
||||
store.commit(`TimelineSpace/HeaderMenu/${HEADER_MUTATION.CHANGE_RELOAD}`, false)
|
||||
})
|
||||
}
|
||||
})
|
||||
watch(
|
||||
notifications,
|
||||
(newState, _oldState) => {
|
||||
if (heading.value && newState.length > 0) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`)
|
||||
if (heading.value && newState.length > 0 && account.account && account.server) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`, account)
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
watch(focusedId, (newVal, _oldVal) => {
|
||||
if (newVal && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if (newVal === null && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
heading.value = true
|
||||
store.commit(`${space}/${ACTION_TYPES.RESET_BADGE}`)
|
||||
}
|
||||
})
|
||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||
if (focusedId.value === null) {
|
||||
focusedId.value = handledNotifications.value[0].id
|
||||
focusedId.value = notifications.value[0].id
|
||||
} else {
|
||||
focusNext()
|
||||
}
|
||||
|
@ -134,9 +138,6 @@ export default defineComponent({
|
|||
whenever(logicAnd(k, shortcutEnabled), () => {
|
||||
focusPrev()
|
||||
})
|
||||
whenever(logicAnd(Ctrl_r, shortcutEnabled), () => {
|
||||
reload()
|
||||
})
|
||||
|
||||
const onScroll = (event: Event) => {
|
||||
if (
|
||||
|
@ -144,43 +145,50 @@ export default defineComponent({
|
|||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||
!lazyLoading.value
|
||||
) {
|
||||
lazyLoading.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_NOTIFICATIONS}`, handledNotifications.value[handledNotifications.value.length - 1])
|
||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_NOTIFICATIONS}`, {
|
||||
lastNotification: notifications.value[notifications.value.length - 1],
|
||||
account: account.account,
|
||||
server: account.server
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.notification_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
lazyLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
heading.value = true
|
||||
store.dispatch(`${space}/${ACTION_TYPES.RESET_BADGE}`)
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`)
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`, account)
|
||||
}
|
||||
}
|
||||
const updateToot = (message: Entity.Status) => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, message)
|
||||
if (account.account) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, { status: message, accountId: account.account.id })
|
||||
}
|
||||
}
|
||||
const fetchNotificationsSince = (since_id: string) => {
|
||||
loadingMore.value = true
|
||||
store.dispatch(`${space}/${ACTION_TYPES.FETCH_NOTIFICATIONS_SINCE}`, since_id).finally(() => {
|
||||
setTimeout(() => {
|
||||
loadingMore.value = false
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
const reload = async () => {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||
try {
|
||||
await reloadable()
|
||||
store.dispatch(`${space}/${ACTION_TYPES.RESET_BADGE}`)
|
||||
} finally {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, false)
|
||||
}
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.FETCH_NOTIFICATIONS_SINCE}`, {
|
||||
sinceId: since_id,
|
||||
account: account.account,
|
||||
server: account.server
|
||||
})
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
loadingMore.value = false
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
const upper = () => {
|
||||
scroller.value.scrollToItem(0)
|
||||
|
@ -188,16 +196,16 @@ export default defineComponent({
|
|||
}
|
||||
const focusNext = () => {
|
||||
if (currentFocusedIndex.value === -1) {
|
||||
focusedId.value = handledNotifications.value[0].id
|
||||
} else if (currentFocusedIndex.value < handledNotifications.value.length) {
|
||||
focusedId.value = handledNotifications.value[currentFocusedIndex.value + 1].id
|
||||
focusedId.value = notifications.value[0].id
|
||||
} else if (currentFocusedIndex.value < notifications.value.length) {
|
||||
focusedId.value = notifications.value[currentFocusedIndex.value + 1].id
|
||||
}
|
||||
}
|
||||
const focusPrev = () => {
|
||||
if (currentFocusedIndex.value === 0) {
|
||||
focusedId.value = null
|
||||
} else if (currentFocusedIndex.value > 0) {
|
||||
focusedId.value = handledNotifications.value[currentFocusedIndex.value - 1].id
|
||||
focusedId.value = notifications.value[currentFocusedIndex.value - 1].id
|
||||
}
|
||||
}
|
||||
const focusNotification = (notification: Entity.Notification) => {
|
||||
|
@ -208,7 +216,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
return {
|
||||
handledNotifications,
|
||||
notifications,
|
||||
loadingMore,
|
||||
fetchNotificationsSince,
|
||||
focusedId,
|
||||
|
@ -221,7 +229,8 @@ export default defineComponent({
|
|||
focusNotification,
|
||||
openSideBar,
|
||||
heading,
|
||||
upper
|
||||
upper,
|
||||
account
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
<template>
|
||||
<div id="public">
|
||||
<div class="unread">{{ unreads.length > 0 ? unreads.length : '' }}</div>
|
||||
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||
<template v-slot="{ item, index, active }">
|
||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||
<toot
|
||||
v-if="account.account && account.server"
|
||||
:message="item"
|
||||
:focused="item.uri + item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
:account="account.account"
|
||||
:server="account.server"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusRight="focusSidebar"
|
||||
|
@ -27,7 +29,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { computed, defineComponent, onBeforeUnmount, onBeforeUpdate, onMounted, onUnmounted, ref, watch, reactive } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
@ -37,12 +39,11 @@ import { useRoute } from 'vue-router'
|
|||
import { useStore } from '@/store'
|
||||
import Toot from '@/components/organisms/Toot.vue'
|
||||
import { EventEmitter } from '@/components/event'
|
||||
import useReloadable from '@/components/utils/reloadable'
|
||||
import { MUTATION_TYPES as SIDE_MENU_MUTATION } from '@/store/TimelineSpace/SideMenu'
|
||||
import { MUTATION_TYPES as TIMELINE_MUTATION, ACTION_TYPES as TIMELINE_ACTION } from '@/store/TimelineSpace'
|
||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||
import { MUTATION_TYPES as CONTENTS_MUTATION } from '@/store/TimelineSpace/Contents'
|
||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Public'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'public',
|
||||
|
@ -52,33 +53,34 @@ export default defineComponent({
|
|||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const i18n = useI18next()
|
||||
const { reloadable } = useReloadable(store, route, i18n)
|
||||
const { j, k, Ctrl_r } = useMagicKeys()
|
||||
const { j, k } = useMagicKeys()
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
|
||||
const focusedId = ref<string | null>(null)
|
||||
const scroller = ref<any>(null)
|
||||
const lazyLoading = ref(false)
|
||||
const heading = ref(true)
|
||||
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
|
||||
account: null,
|
||||
server: null
|
||||
})
|
||||
|
||||
const timeline = computed(() => store.state.TimelineSpace.Contents.Public.timeline)
|
||||
const unreads = computed(() => store.state.TimelineSpace.Contents.Public.unreads)
|
||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.Public.lazyLoading)
|
||||
const heading = computed(() => store.state.TimelineSpace.Contents.Public.heading)
|
||||
const timeline = computed(() => store.state.TimelineSpace.Contents.Public.timeline[id.value])
|
||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||
const unreadNotification = computed(() => store.state.TimelineSpace.timelineSetting.unreadNotification)
|
||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||
const filters = computed(() => store.getters[`${space}/filters`])
|
||||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||
|
||||
onMounted(async () => {
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
account.account = a
|
||||
account.server = s
|
||||
|
||||
store.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_PUBLIC_TIMELINE}`, false)
|
||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||
if (!unreadNotification.value.public) {
|
||||
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true)
|
||||
await initialize().finally(() => {
|
||||
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, false)
|
||||
})
|
||||
}
|
||||
})
|
||||
onBeforeUpdate(() => {
|
||||
if (store.state.TimelineSpace.SideMenu.unreadPublicTimeline && heading.value) {
|
||||
|
@ -86,37 +88,21 @@ export default defineComponent({
|
|||
}
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
if (!unreadNotification.value.public) {
|
||||
store.dispatch(`TimelineSpace/${TIMELINE_ACTION.STOP_PUBLIC_STREAMING}`)
|
||||
store.dispatch(`TimelineSpace/${TIMELINE_ACTION.UNBIND_PUBLIC_STREAMING}`)
|
||||
}
|
||||
EventEmitter.off('focus-timeline')
|
||||
})
|
||||
onUnmounted(() => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_TIMELINE}`)
|
||||
if (!unreadNotification.value.public) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CLEAR_TIMELINE}`)
|
||||
}
|
||||
const el = document.getElementById('scroller')
|
||||
if (el !== undefined && el !== null) {
|
||||
el.removeEventListener('scroll', onScroll)
|
||||
el.scrollTop = 0
|
||||
}
|
||||
})
|
||||
watch(startReload, (newVal, oldVal) => {
|
||||
if (!oldVal && newVal) {
|
||||
reload().finally(() => {
|
||||
store.commit(`TimelineSpace/HeaderMenu/${HEADER_MUTATION.CHANGE_RELOAD}`, false)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
watch(focusedId, (newVal, _oldVal) => {
|
||||
if (newVal && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if (newVal === null && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
heading.value = true
|
||||
}
|
||||
})
|
||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||
|
@ -129,54 +115,47 @@ export default defineComponent({
|
|||
whenever(logicAnd(k, shortcutEnabled), () => {
|
||||
focusPrev()
|
||||
})
|
||||
whenever(logicAnd(Ctrl_r, shortcutEnabled), () => {
|
||||
reload()
|
||||
})
|
||||
|
||||
const initialize = async () => {
|
||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_PUBLIC_TIMELINE}`).catch(_ => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
await store.dispatch(`TimelineSpace/${TIMELINE_ACTION.BIND_PUBLIC_STREAMING}`)
|
||||
store.dispatch(`TimelineSpace/${TIMELINE_ACTION.START_PUBLIC_STREAMING}`)
|
||||
}
|
||||
const onScroll = (event: Event) => {
|
||||
if (
|
||||
(event.target as HTMLElement)!.clientHeight + (event.target as HTMLElement)!.scrollTop >=
|
||||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||
!lazyLoading.value
|
||||
) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, timeline.value[timeline.value.length - 1]).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
lazyLoading.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||
statuses: timeline.value[timeline.value.length - 1],
|
||||
account: account.account,
|
||||
server: account.server
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
lazyLoading.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.MERGE_UNREADS}`)
|
||||
}
|
||||
}
|
||||
const reload = async () => {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||
try {
|
||||
await reloadable()
|
||||
} finally {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, false)
|
||||
heading.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const updateToot = (message: Entity.Status) => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, message)
|
||||
if (account.account) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, { status: message, accountId: account.account.id })
|
||||
}
|
||||
}
|
||||
const deleteToot = (message: Entity.Status) => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.DELETE_TOOT}`, message.id)
|
||||
if (account.account) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.DELETE_TOOT}`, { statusId: message.id, accountId: account.account.id })
|
||||
}
|
||||
}
|
||||
const upper = () => {
|
||||
scroller.value.scrollToItem(0)
|
||||
|
@ -216,7 +195,7 @@ export default defineComponent({
|
|||
openSideBar,
|
||||
heading,
|
||||
upper,
|
||||
unreads
|
||||
account
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -87,9 +87,6 @@ export default defineComponent({
|
|||
case 'bookmarks':
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TITLE}`, i18n.t('header_menu.bookmark'))
|
||||
break
|
||||
case 'mentions':
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TITLE}`, i18n.t('header_menu.mention'))
|
||||
break
|
||||
case 'follow-requests':
|
||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TITLE}`, i18n.t('header_menu.follow_requests'))
|
||||
break
|
||||
|
@ -131,16 +128,10 @@ export default defineComponent({
|
|||
}
|
||||
const reload = () => {
|
||||
switch (route.name) {
|
||||
case 'home':
|
||||
case 'notifications':
|
||||
case 'mentions':
|
||||
case 'favourites':
|
||||
case 'bookmarks':
|
||||
case 'local':
|
||||
case 'public':
|
||||
case 'tag':
|
||||
case 'list':
|
||||
case 'direct-messages':
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_RELOAD}`, true)
|
||||
break
|
||||
default:
|
||||
|
@ -149,16 +140,10 @@ export default defineComponent({
|
|||
}
|
||||
const reloadable = () => {
|
||||
switch (route.name) {
|
||||
case 'home':
|
||||
case 'notifications':
|
||||
case 'mentions':
|
||||
case 'favourites':
|
||||
case 'bookmarks':
|
||||
case 'local':
|
||||
case 'public':
|
||||
case 'tag':
|
||||
case 'list':
|
||||
case 'direct-messages':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
|
|
@ -37,11 +37,7 @@ export default defineComponent({
|
|||
|
||||
const channelForm = ref<HTMLInputElement>()
|
||||
|
||||
const channelList = computed(() =>
|
||||
store.state.TimelineSpace.Modals.Jump.defaultChannelList
|
||||
.concat(store.state.TimelineSpace.Modals.Jump.tagChannelList)
|
||||
.concat(store.state.TimelineSpace.Modals.Jump.listChannelList)
|
||||
)
|
||||
const channelList = computed(() => store.state.TimelineSpace.Modals.Jump.defaultChannelList)
|
||||
const selectedChannel = computed(() => store.state.TimelineSpace.Modals.Jump.selectedChannel)
|
||||
const inputtedChannel = computed({
|
||||
get: () => store.state.TimelineSpace.Modals.Jump.channel,
|
||||
|
@ -57,8 +53,6 @@ export default defineComponent({
|
|||
)
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SYNC_LIST_CHANNEL}`)
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SYNC_TAG_CHANNEL}`)
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
channelForm.value?.focus()
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
<div :class="collapse ? 'profile-narrow' : 'profile-wide'">
|
||||
<div class="account">
|
||||
<div class="avatar" v-if="collapse">
|
||||
<img :src="account.avatar" />
|
||||
<img :src="account.account?.avatar" />
|
||||
</div>
|
||||
<div class="acct" v-else>
|
||||
@{{ account.username }}
|
||||
<span class="domain-name">{{ account.domain }}</span>
|
||||
@{{ account.account?.username }}
|
||||
<span class="domain-name">{{ account.server?.domain }}</span>
|
||||
</div>
|
||||
<el-dropdown trigger="click" @command="handleProfile" :title="$t('side_menu.profile')">
|
||||
<span class="el-dropdown-link">
|
||||
|
@ -71,23 +71,6 @@
|
|||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item
|
||||
:index="`/${id}/mentions`"
|
||||
role="menuitem"
|
||||
:title="$t('side_menu.mention')"
|
||||
class="menu-item"
|
||||
v-if="enabledTimelines.mention"
|
||||
>
|
||||
<div class="menu-item-icon">
|
||||
<font-awesome-icon icon="at" />
|
||||
</div>
|
||||
<template #title>
|
||||
<div>
|
||||
<span>{{ $t('side_menu.mention') }}</span>
|
||||
<el-badge is-dot :hidden="!unreadMentions"> </el-badge>
|
||||
</div>
|
||||
</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item
|
||||
:index="`/${id}/direct-messages`"
|
||||
role="menuitem"
|
||||
|
@ -263,13 +246,18 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed, onMounted } from 'vue'
|
||||
import { defineComponent, computed, onMounted, ref, reactive } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useStore } from '@/store'
|
||||
import { ACTION_TYPES } from '@/store/TimelineSpace/SideMenu'
|
||||
import { ACTION_TYPES as PROFILE_ACTION } from '@/store/TimelineSpace/Contents/SideBar/AccountProfile'
|
||||
import { ACTION_TYPES as SIDEBAR_ACTION, MUTATION_TYPES as SIDEBAR_MUTATION } from '@/store/TimelineSpace/Contents/SideBar'
|
||||
import { ACTION_TYPES as GLOBAL_ACTION } from '@/store/GlobalHeader'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import generator, { Entity, MegalodonInterface } from 'megalodon'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { LocalTag } from '~/src/types/localTag'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'side-menu',
|
||||
|
@ -279,33 +267,102 @@ export default defineComponent({
|
|||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
|
||||
const lists = ref<Array<Entity.List>>([])
|
||||
const tags = ref<Array<LocalTag>>([])
|
||||
const enabledTimelines = reactive({
|
||||
home: true,
|
||||
notification: true,
|
||||
direct: true,
|
||||
favourite: true,
|
||||
bookmark: true,
|
||||
local: true,
|
||||
public: true,
|
||||
tag: true,
|
||||
list: true
|
||||
})
|
||||
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
|
||||
account: null,
|
||||
server: null
|
||||
})
|
||||
|
||||
const unreadHomeTimeline = computed(() => store.state.TimelineSpace.SideMenu.unreadHomeTimeline)
|
||||
const unreadNotifications = computed(() => store.state.TimelineSpace.SideMenu.unreadNotifications)
|
||||
const unreadMentions = computed(() => store.state.TimelineSpace.SideMenu.unreadMentions)
|
||||
const unreadLocalTimeline = computed(() => store.state.TimelineSpace.SideMenu.unreadLocalTimeline)
|
||||
const unreadDirectMessagesTimeline = computed(() => store.state.TimelineSpace.SideMenu.unreadDirectMessagesTimeline)
|
||||
const unreadPublicTimeline = computed(() => store.state.TimelineSpace.SideMenu.unreadPublicTimeline)
|
||||
const unreadFollowRequests = computed(() => store.state.TimelineSpace.SideMenu.unreadFollowRequests)
|
||||
const lists = computed(() => store.state.TimelineSpace.SideMenu.lists)
|
||||
const tags = computed(() => store.state.TimelineSpace.SideMenu.tags)
|
||||
const collapse = computed(() => store.state.TimelineSpace.SideMenu.collapse)
|
||||
const enabledTimelines = computed(() => store.state.TimelineSpace.SideMenu.enabledTimelines)
|
||||
const account = computed(() => store.state.TimelineSpace.account)
|
||||
const themeColor = computed(() => store.state.App.theme.side_menu_color)
|
||||
const hideGlobalHeader = computed(() => store.state.GlobalHeader.hide)
|
||||
const userAgent = computed(() => store.state.App.userAgent)
|
||||
const activeRoute = computed(() => route.path)
|
||||
const id = computed(() => route.params.id)
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.READ_COLLAPSE}`)
|
||||
store.dispatch(`${space}/${ACTION_TYPES.LIST_TAGS}`)
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
account.account = a
|
||||
account.server = s
|
||||
|
||||
const client = generator(s.sns, s.baseURL, a.accessToken, userAgent.value)
|
||||
await fetchLists(client)
|
||||
await fetchTags(a)
|
||||
await confirmTimelines(client)
|
||||
})
|
||||
|
||||
const fetchLists = async (client: MegalodonInterface) => {
|
||||
const res = await client.getLists()
|
||||
lists.value = res.data
|
||||
}
|
||||
|
||||
const fetchTags = async (account: LocalAccount) => {
|
||||
tags.value = await win.ipcRenderer.invoke('list-hashtags', account.id)
|
||||
}
|
||||
|
||||
const confirmTimelines = async (client: MegalodonInterface) => {
|
||||
const notification = async () => {
|
||||
return client.getNotifications({ limit: 1 }).catch(() => {
|
||||
enabledTimelines.notification = false
|
||||
})
|
||||
}
|
||||
const direct = async () => {
|
||||
return client.getConversationTimeline({ limit: 1 }).catch(() => {
|
||||
enabledTimelines.direct = false
|
||||
})
|
||||
}
|
||||
const favourite = async () => {
|
||||
return client.getFavourites({ limit: 1 }).catch(() => {
|
||||
enabledTimelines.favourite = false
|
||||
})
|
||||
}
|
||||
const bookmark = async () => {
|
||||
return client.getBookmarks({ limit: 1 }).catch(() => {
|
||||
enabledTimelines.bookmark = false
|
||||
})
|
||||
}
|
||||
const local = async () => {
|
||||
return client.getLocalTimeline({ limit: 1 }).catch(() => {
|
||||
enabledTimelines.local = false
|
||||
})
|
||||
}
|
||||
const pub = async () => {
|
||||
return client.getPublicTimeline({ limit: 1 }).catch(() => {
|
||||
enabledTimelines.public = false
|
||||
})
|
||||
}
|
||||
await Promise.all([notification(), direct(), favourite(), bookmark(), local(), pub()])
|
||||
}
|
||||
|
||||
const handleProfile = (command: string) => {
|
||||
switch (command) {
|
||||
case 'show':
|
||||
if (!account.account) {
|
||||
return
|
||||
}
|
||||
store
|
||||
.dispatch(`TimelineSpace/Contents/SideBar/AccountProfile/${PROFILE_ACTION.FETCH_ACCOUNT}`, account.value.accountId)
|
||||
.dispatch(`TimelineSpace/Contents/SideBar/AccountProfile/${PROFILE_ACTION.FETCH_ACCOUNT}`, account.account.accountId)
|
||||
.then(account => {
|
||||
store.dispatch(`TimelineSpace/Contents/SideBar/AccountProfile/${PROFILE_ACTION.CHANGE_ACCOUNT}`, account)
|
||||
store.commit(`TimelineSpace/Contents/SideBar/${SIDEBAR_MUTATION.CHANGE_OPEN_SIDEBAR}`, true)
|
||||
|
@ -314,7 +371,9 @@ export default defineComponent({
|
|||
store.dispatch(`TimelineSpace/Contents/SideBar/${SIDEBAR_ACTION.OPEN_ACCOUNT_COMPONENT}`)
|
||||
break
|
||||
case 'edit':
|
||||
;(window as any).shell.openExternal(account.value.baseURL + '/settings/profile')
|
||||
if (account.server) {
|
||||
;(window as any).shell.openExternal(account.server.baseURL + '/settings/profile')
|
||||
}
|
||||
break
|
||||
case 'settings': {
|
||||
const url = `/${id.value}/settings`
|
||||
|
@ -336,7 +395,6 @@ export default defineComponent({
|
|||
return {
|
||||
unreadHomeTimeline,
|
||||
unreadNotifications,
|
||||
unreadMentions,
|
||||
unreadLocalTimeline,
|
||||
unreadDirectMessagesTimeline,
|
||||
unreadPublicTimeline,
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
:account="account"
|
||||
:server="server"
|
||||
@update="updateToot"
|
||||
@delete="deleteToot"
|
||||
@focus-right="$emit('focusRight')"
|
||||
|
@ -69,6 +71,8 @@
|
|||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
:account="account"
|
||||
:server="server"
|
||||
@focus-right="$emit('focusRight')"
|
||||
@select="$emit('selectNotification')"
|
||||
>
|
||||
|
@ -117,6 +121,8 @@ import Follow from './Notification/Follow.vue'
|
|||
import FollowRequest from './Notification/FollowRequest.vue'
|
||||
import Mention from './Notification/Mention.vue'
|
||||
import Status from './Notification/Status.vue'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Notification',
|
||||
|
@ -143,6 +149,14 @@ export default defineComponent({
|
|||
overlaid: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
account: {
|
||||
type: Object as PropType<LocalAccount>,
|
||||
required: true
|
||||
},
|
||||
server: {
|
||||
type: Object as PropType<LocalServer>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['focusRight', 'selectNotification', 'update', 'delete'],
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
:account="account"
|
||||
:server="server"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusRight="$emit('focusRight')"
|
||||
|
@ -18,6 +20,8 @@
|
|||
import { defineComponent, PropType } from 'vue'
|
||||
import { Entity } from 'megalodon'
|
||||
import Toot from '../Toot.vue'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'mention',
|
||||
|
@ -37,6 +41,14 @@ export default defineComponent({
|
|||
overlaid: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
account: {
|
||||
type: Object as PropType<LocalAccount>,
|
||||
required: true
|
||||
},
|
||||
server: {
|
||||
type: Object as PropType<LocalServer>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: { Toot },
|
||||
|
|
|
@ -26,6 +26,8 @@
|
|||
:filters="filters"
|
||||
:focused="focused"
|
||||
:overlaid="overlaid"
|
||||
:account="account"
|
||||
:server="server"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusRight="$emit('focusRight')"
|
||||
|
@ -45,6 +47,8 @@ import Toot from '../Toot.vue'
|
|||
import { usernameWithStyle } from '@/utils/username'
|
||||
import { MUTATION_TYPES as SIDEBAR_MUTATION, ACTION_TYPES as SIDEBAR_ACTION } from '@/store/TimelineSpace/Contents/SideBar'
|
||||
import { ACTION_TYPES as PROFILE_ACTION } from '@/store/TimelineSpace/Contents/SideBar/AccountProfile'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'mention',
|
||||
|
@ -64,6 +68,14 @@ export default defineComponent({
|
|||
overlaid: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
account: {
|
||||
type: Object as PropType<LocalAccount>,
|
||||
required: true
|
||||
},
|
||||
server: {
|
||||
type: Object as PropType<LocalServer>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: { Toot, FailoverImg },
|
||||
|
|
|
@ -137,7 +137,6 @@
|
|||
{{ favouritesCount }}
|
||||
</div>
|
||||
<el-button
|
||||
v-if="bookmarkSupported"
|
||||
:class="originalMessage.bookmarked ? 'bookmarked' : 'bookmark'"
|
||||
link
|
||||
:title="$t('cards.toot.bookmark')"
|
||||
|
@ -149,7 +148,7 @@
|
|||
<el-button v-if="quoteSupported" link class="quote-btn" @click="openQuote()">
|
||||
<font-awesome-icon icon="quote-right" size="sm" />
|
||||
</el-button>
|
||||
<template v-if="sns !== 'mastodon'">
|
||||
<template v-if="server!.sns !== 'mastodon'">
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
width="281"
|
||||
|
@ -262,6 +261,8 @@ import { ACTION_TYPES as REPORT_ACTION } from '@/store/TimelineSpace/Modals/Repo
|
|||
import { ACTION_TYPES as MUTE_ACTION } from '@/store/TimelineSpace/Modals/MuteConfirm'
|
||||
import { ACTION_TYPES as VIEWER_ACTION } from '@/store/TimelineSpace/Modals/ImageViewer'
|
||||
import { ACTION_TYPES } from '@/store/organisms/Toot'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
|
||||
const defaultEmojiIndex = new EmojiIndex(data)
|
||||
|
||||
|
@ -298,16 +299,24 @@ export default defineComponent({
|
|||
detailed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
account: {
|
||||
type: Object as PropType<LocalAccount>,
|
||||
required: true
|
||||
},
|
||||
server: {
|
||||
type: Object as PropType<LocalServer>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['selectToot', 'focusRight', 'focusLeft'],
|
||||
emits: ['selectToot', 'focusRight', 'focusLeft', 'update', 'delete', 'sizeChanged'],
|
||||
setup(props, ctx) {
|
||||
const space = 'organisms/Toot'
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const i18n = useI18next()
|
||||
const { focused, overlaid, message, filters } = toRefs(props)
|
||||
const { focused, overlaid, message, filters, account, server } = toRefs(props)
|
||||
const { l, h, r, b, f, o, p, i, x } = useMagicKeys()
|
||||
|
||||
const statusRef = ref<any>(null)
|
||||
|
@ -320,9 +329,6 @@ export default defineComponent({
|
|||
const displayNameStyle = computed(() => store.state.App.displayNameStyle)
|
||||
const timeFormat = computed(() => store.state.App.timeFormat)
|
||||
const language = computed(() => store.state.App.language)
|
||||
const sns = computed(() => store.state.TimelineSpace.sns)
|
||||
const account = computed(() => store.state.TimelineSpace.account)
|
||||
const bookmarkSupported = computed(() => store.state.TimelineSpace.SideMenu.enabledTimelines.bookmark)
|
||||
const shortcutEnabled = computed(() => focused.value && !overlaid.value)
|
||||
const originalMessage = computed(() => {
|
||||
if (message.value.reblog && !message.value.quote) {
|
||||
|
@ -352,7 +358,7 @@ export default defineComponent({
|
|||
return null
|
||||
})
|
||||
const isMyMessage = computed(() => {
|
||||
return store.state.TimelineSpace.account.accountId === originalMessage.value.account.id
|
||||
return account.value.accountId === originalMessage.value.account.id
|
||||
})
|
||||
const application = computed(() => {
|
||||
const msg = originalMessage.value
|
||||
|
@ -386,7 +392,7 @@ export default defineComponent({
|
|||
return originalMessage.value.visibility === 'direct'
|
||||
})
|
||||
const quoteSupported = computed(() => {
|
||||
return QuoteSupported(sns.value, account.value.domain)
|
||||
return QuoteSupported(server.value.sns, server.value.domain)
|
||||
})
|
||||
|
||||
whenever(logicAnd(l, shortcutEnabled), () => {
|
||||
|
@ -677,9 +683,6 @@ export default defineComponent({
|
|||
displayNameStyle,
|
||||
timeFormat,
|
||||
language,
|
||||
sns,
|
||||
account,
|
||||
bookmarkSupported,
|
||||
originalMessage,
|
||||
timestamp,
|
||||
readableTimestamp,
|
||||
|
|
|
@ -4,7 +4,6 @@ import { RouteLocationNormalizedLoaded } from 'vue-router'
|
|||
import { i18n } from 'i18next'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ACTION_TYPES } from '@/store/TimelineSpace'
|
||||
import { ACTION_TYPES as GLOBAL_ACTION } from '@/store/GlobalHeader'
|
||||
|
||||
export default function useReloadable(store: Store<RootState>, route: RouteLocationNormalizedLoaded, i18next: i18n) {
|
||||
async function reloadable() {
|
||||
|
@ -15,11 +14,6 @@ export default function useReloadable(store: Store<RootState>, route: RouteLocat
|
|||
})
|
||||
throw err
|
||||
})
|
||||
await store.dispatch(`GlobalHeader/${GLOBAL_ACTION.STOP_USER_STREAMINGS}`)
|
||||
await store.dispatch(`TimelineSpace/${ACTION_TYPES.STOP_STREAMINGS}`)
|
||||
await store.dispatch(`TimelineSpace/${ACTION_TYPES.FETCH_CONTENTS_TIMELINES}`)
|
||||
await store.dispatch(`TimelineSpace/${ACTION_TYPES.START_STREAMINGS}`)
|
||||
store.dispatch(`GlobalHeader/${GLOBAL_ACTION.START_USER_STREAMINGS}`)
|
||||
return account
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
|
||||
import Login from '@/components/Login.vue'
|
||||
import Authorize from '@/components/Authorize.vue'
|
||||
import Preferences from '@/components/Preferences.vue'
|
||||
import PreferencesGeneral from '@/components/Preferences/General.vue'
|
||||
import PreferencesAppearance from '@/components/Preferences/Appearance.vue'
|
||||
|
@ -19,7 +18,6 @@ import SettingsFiltersNew from '@/components/Settings/Filters/New.vue'
|
|||
import TimelineSpace from '@/components/TimelineSpace.vue'
|
||||
import TimelineSpaceContentsHome from '@/components/TimelineSpace/Contents/Home.vue'
|
||||
import TimelineSpaceContentsNotifications from '@/components/TimelineSpace/Contents/Notifications.vue'
|
||||
import TimelineSpaceContentsMentions from '@/components/TimelineSpace/Contents/Mentions.vue'
|
||||
import TimelineSpaceContentsFavourites from '@/components/TimelineSpace/Contents/Favourites.vue'
|
||||
import TimelineSpaceContentsLocal from '@/components/TimelineSpace/Contents/Local.vue'
|
||||
import TimelineSpaceContentsPublic from '@/components/TimelineSpace/Contents/Public.vue'
|
||||
|
@ -40,12 +38,6 @@ const routes = [
|
|||
name: 'login',
|
||||
component: Login
|
||||
},
|
||||
{
|
||||
path: '/authorize',
|
||||
name: 'authorize',
|
||||
component: Authorize,
|
||||
props: route => ({ url: route.query.url, sns: route.query.sns })
|
||||
},
|
||||
{
|
||||
path: '/preferences/',
|
||||
name: 'preferences',
|
||||
|
@ -130,11 +122,6 @@ const routes = [
|
|||
name: 'notifications',
|
||||
component: TimelineSpaceContentsNotifications
|
||||
},
|
||||
{
|
||||
path: 'mentions',
|
||||
name: 'mentions',
|
||||
component: TimelineSpaceContentsMentions
|
||||
},
|
||||
{
|
||||
path: 'follow-requests',
|
||||
name: 'follow-requests',
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
import { Module, ActionTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
|
||||
const win = window as any as MyWindow
|
||||
|
||||
export type AuthorizeState = {}
|
||||
|
||||
const state = (): AuthorizeState => ({})
|
||||
|
||||
export const ACTION_TYPES = {
|
||||
SUBMIT: 'submit'
|
||||
}
|
||||
|
||||
const actions: ActionTree<AuthorizeState, RootState> = {
|
||||
[ACTION_TYPES.SUBMIT]: async (_, request: { code: string | null; sns: 'mastodon' | 'pleroma' | 'misskey' }): Promise<string> => {
|
||||
let req = {
|
||||
sns: request.sns
|
||||
}
|
||||
if (request.code) {
|
||||
req = Object.assign(req, {
|
||||
code: request.code.trim()
|
||||
})
|
||||
}
|
||||
const id = await win.ipcRenderer.invoke('get-and-update-access-token', req)
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
const Authorize: Module<AuthorizeState, RootState> = {
|
||||
namespaced: true,
|
||||
state: state,
|
||||
mutations: {},
|
||||
actions: actions
|
||||
}
|
||||
|
||||
export default Authorize
|
|
@ -2,13 +2,13 @@ import router from '@/router'
|
|||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
import { StreamingError } from '~src/errors/streamingError'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { LocalServer } from '~src/types/localServer'
|
||||
|
||||
const win = window as any as MyWindow
|
||||
const win = (window as any) as MyWindow
|
||||
|
||||
export type GlobalHeaderState = {
|
||||
accounts: Array<LocalAccount>
|
||||
accounts: Array<[LocalAccount, LocalServer]>
|
||||
changing: boolean
|
||||
hide: boolean
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ export const MUTATION_TYPES = {
|
|||
}
|
||||
|
||||
const mutations: MutationTree<GlobalHeaderState> = {
|
||||
[MUTATION_TYPES.UPDATE_ACCOUNTS]: (state: GlobalHeaderState, accounts: Array<LocalAccount>) => {
|
||||
[MUTATION_TYPES.UPDATE_ACCOUNTS]: (state: GlobalHeaderState, accounts: Array<[LocalAccount, LocalServer]>) => {
|
||||
state.accounts = accounts
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_CHANGING]: (state: GlobalHeaderState, value: boolean) => {
|
||||
|
@ -41,27 +41,28 @@ export const ACTION_TYPES = {
|
|||
INIT_LOAD: 'initLoad',
|
||||
START_STREAMINGS: 'startStreamings',
|
||||
LIST_ACCOUNTS: 'listAccounts',
|
||||
REFRESH_ACCOUNTS: 'refreshAccounts',
|
||||
WATCH_SHORTCUT_EVENTS: 'watchShortcutEvents',
|
||||
REMOVE_SHORTCUT_EVENTS: 'removeShortcutEvents',
|
||||
LOAD_HIDE: 'loadHide',
|
||||
SWITCH_HIDE: 'switchHide',
|
||||
START_USER_STREAMINGS: 'startUserStreamings',
|
||||
STOP_USER_STREAMINGS: 'stopUserStreamings',
|
||||
LOAD_TIMELINES: 'loadTimelines',
|
||||
BIND_STREAMINGS: 'bindStreamings',
|
||||
BIND_NOTIFICATION: 'bindNotification'
|
||||
}
|
||||
|
||||
const actions: ActionTree<GlobalHeaderState, RootState> = {
|
||||
initLoad: async ({ dispatch }): Promise<Array<LocalAccount>> => {
|
||||
[ACTION_TYPES.INIT_LOAD]: async ({ dispatch }): Promise<Array<LocalAccount>> => {
|
||||
// Ignore error
|
||||
try {
|
||||
await dispatch('removeShortcutEvents')
|
||||
await dispatch('loadHide')
|
||||
dispatch('watchShortcutEvents')
|
||||
await dispatch(ACTION_TYPES.REMOVE_SHORTCUT_EVENTS)
|
||||
await dispatch(ACTION_TYPES.LOAD_HIDE)
|
||||
dispatch(ACTION_TYPES.WATCH_SHORTCUT_EVENTS)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
const accounts = await dispatch('listAccounts')
|
||||
const accounts = await dispatch(ACTION_TYPES.LIST_ACCOUNTS)
|
||||
await dispatch(ACTION_TYPES.LOAD_TIMELINES, accounts)
|
||||
await dispatch(ACTION_TYPES.BIND_STREAMINGS, accounts)
|
||||
// Block to root path when user use browser-back, like mouse button.
|
||||
// Because any contents are not rendered when browser back to / from home.
|
||||
router.beforeEach((to, from, next) => {
|
||||
|
@ -71,28 +72,17 @@ const actions: ActionTree<GlobalHeaderState, RootState> = {
|
|||
})
|
||||
return accounts
|
||||
},
|
||||
startStreamings: async ({ dispatch }) => {
|
||||
dispatch('bindNotification')
|
||||
dispatch('startUserStreamings')
|
||||
},
|
||||
listAccounts: async ({ dispatch, commit }): Promise<Array<LocalAccount>> => {
|
||||
const accounts = await win.ipcRenderer.invoke('list-accounts')
|
||||
commit(MUTATION_TYPES.UPDATE_ACCOUNTS, accounts)
|
||||
dispatch('refreshAccounts')
|
||||
return accounts
|
||||
},
|
||||
// Fetch account informations and save current state when GlobalHeader is displayed
|
||||
refreshAccounts: async ({ commit }): Promise<Array<LocalAccount>> => {
|
||||
const accounts: Array<LocalAccount> = await win.ipcRenderer.invoke('refresh-accounts')
|
||||
[ACTION_TYPES.LIST_ACCOUNTS]: async ({ commit }): Promise<Array<[LocalAccount, LocalServer]>> => {
|
||||
const accounts: Array<[LocalAccount, LocalServer]> = await win.ipcRenderer.invoke('list-accounts')
|
||||
commit(MUTATION_TYPES.UPDATE_ACCOUNTS, accounts)
|
||||
return accounts
|
||||
},
|
||||
watchShortcutEvents: ({ state, commit, rootState, rootGetters }) => {
|
||||
[ACTION_TYPES.WATCH_SHORTCUT_EVENTS]: ({ state, commit, rootState, rootGetters }) => {
|
||||
win.ipcRenderer.on('change-account', (_, account: LocalAccount) => {
|
||||
if (state.changing) {
|
||||
return null
|
||||
}
|
||||
if ((rootState.route.params.id as string) === account._id!) {
|
||||
if ((rootState.route.params.id as string) === account[0].id) {
|
||||
return null
|
||||
}
|
||||
// When the modal window is active, don't change account
|
||||
|
@ -101,46 +91,81 @@ const actions: ActionTree<GlobalHeaderState, RootState> = {
|
|||
}
|
||||
// changing finish after loading
|
||||
commit(MUTATION_TYPES.UPDATE_CHANGING, true)
|
||||
router.push(`/${account._id}/home`)
|
||||
router.push(`/${account[0].id}/home`)
|
||||
return true
|
||||
})
|
||||
},
|
||||
removeShortcutEvents: async () => {
|
||||
[ACTION_TYPES.REMOVE_SHORTCUT_EVENTS]: async () => {
|
||||
win.ipcRenderer.removeAllListeners('change-account')
|
||||
return true
|
||||
},
|
||||
loadHide: async ({ commit }): Promise<boolean> => {
|
||||
[ACTION_TYPES.LOAD_HIDE]: async ({ commit }): Promise<boolean> => {
|
||||
const hide: boolean = await win.ipcRenderer.invoke('get-global-header')
|
||||
commit(MUTATION_TYPES.CHANGE_HIDE, hide)
|
||||
return hide
|
||||
},
|
||||
switchHide: async ({ dispatch }, hide: boolean): Promise<boolean> => {
|
||||
[ACTION_TYPES.SWITCH_HIDE]: async ({ dispatch }, hide: boolean): Promise<boolean> => {
|
||||
await win.ipcRenderer.invoke('change-global-header', hide)
|
||||
dispatch('loadHide')
|
||||
dispatch(ACTION_TYPES.LOAD_HIDE)
|
||||
return true
|
||||
},
|
||||
startUserStreamings: ({ state }): Promise<{}> => {
|
||||
// @ts-ignore
|
||||
return new Promise((resolve, reject) => {
|
||||
win.ipcRenderer.once('error-start-all-user-streamings', (_, err: StreamingError) => {
|
||||
reject(err)
|
||||
})
|
||||
win.ipcRenderer.send(
|
||||
'start-all-user-streamings',
|
||||
state.accounts.map(a => a._id)
|
||||
)
|
||||
})
|
||||
},
|
||||
stopUserStreamings: () => {
|
||||
win.ipcRenderer.send('stop-all-user-streamings')
|
||||
},
|
||||
bindNotification: () => {
|
||||
[ACTION_TYPES.BIND_NOTIFICATION]: () => {
|
||||
win.ipcRenderer.removeAllListeners('open-notification-tab')
|
||||
win.ipcRenderer.on('open-notification-tab', (_, id: string) => {
|
||||
router.push(`/${id}/home`)
|
||||
// We have to wait until change el-menu-item
|
||||
setTimeout(() => router.push(`/${id}/notifications`), 500)
|
||||
})
|
||||
},
|
||||
[ACTION_TYPES.LOAD_TIMELINES]: async ({ dispatch }, req: Array<[LocalAccount, LocalServer]>) => {
|
||||
req.forEach(async ([account, server]) => {
|
||||
await dispatch('TimelineSpace/Contents/Home/fetchTimeline', { account, server }, { root: true })
|
||||
await dispatch('TimelineSpace/Contents/Notifications/fetchNotifications', { account, server }, { root: true })
|
||||
await dispatch('TimelineSpace/Contents/Local/fetchLocalTimeline', { account, server }, { root: true })
|
||||
await dispatch('TimelineSpace/Contents/Public/fetchPublicTimeline', { account, server }, { root: true })
|
||||
await dispatch('TimelineSpace/Contents/DirectMessages/fetchTimeline', { account, server }, { root: true })
|
||||
})
|
||||
},
|
||||
[ACTION_TYPES.BIND_STREAMINGS]: async ({ commit }, req: Array<[LocalAccount, LocalServer]>) => {
|
||||
req.forEach(async ([account, _server]) => {
|
||||
win.ipcRenderer.removeAllListeners(`update-user-streamings-${account.id}`)
|
||||
win.ipcRenderer.on(`update-user-streamings-${account.id}`, (_, update: Entity.Status) => {
|
||||
commit('TimelineSpace/Contents/Home/appendTimeline', { status: update, accountId: account.id }, { root: true })
|
||||
})
|
||||
win.ipcRenderer.removeAllListeners(`notification-user-streamings-${account.id}`)
|
||||
win.ipcRenderer.on(`notification-user-streamings-${account.id}`, (_, notification: Entity.Notification) => {
|
||||
commit('TimelineSpace/Contents/Notifications/appendNotifications', { notification, accountId: account.id }, { root: true })
|
||||
})
|
||||
win.ipcRenderer.removeAllListeners(`delete-user-streamings-${account.id}`)
|
||||
win.ipcRenderer.on(`delete-user-streamings-${account.id}`, (_, id: string) => {
|
||||
commit('TimelineSpace/Contents/Home/deleteToot', { statusId: id, accountId: account.id }, { root: true })
|
||||
commit('TimelineSpace/Contents/Notifications/deleteToot', { statusId: id, accountId: account.id }, { root: true })
|
||||
})
|
||||
win.ipcRenderer.removeAllListeners(`update-local-streamings-${account.id}`)
|
||||
win.ipcRenderer.on(`update-local-streamings-${account.id}`, (_, update: Entity.Status) => {
|
||||
commit('TimelineSpace/Contents/Local/appendTimeline', { status: update, accountId: account.id }, { root: true })
|
||||
})
|
||||
win.ipcRenderer.removeAllListeners(`delete-local-streamings-${account.id}`)
|
||||
win.ipcRenderer.on(`delete-local-streamings-${account.id}`, (_, id: string) => {
|
||||
commit('TimelineSpace/Contents/Local/deleteToot', { statusId: id, accountId: account.id }, { root: true })
|
||||
})
|
||||
win.ipcRenderer.removeAllListeners(`update-public-streamings-${account.id}`)
|
||||
win.ipcRenderer.on(`update-public-streamings-${account.id}`, (_, update: Entity.Status) => {
|
||||
commit('TimelineSpace/Contents/Public/appendTimeline', { status: update, accountId: account.id }, { root: true })
|
||||
})
|
||||
win.ipcRenderer.removeAllListeners(`delete-public-streamings-${account.id}`)
|
||||
win.ipcRenderer.on(`delete-public-streamings-${account.id}`, (_, id: string) => {
|
||||
commit('TimelineSpace/Contents/Public/deleteToot', { statusId: id, accountId: account.id }, { root: true })
|
||||
})
|
||||
win.ipcRenderer.removeAllListeners(`update-direct-streamings-${account.id}`)
|
||||
win.ipcRenderer.on(`update-direct-streamings-${account.id}`, (_, update: Entity.Status) => {
|
||||
commit('TimelineSpace/Contents/DirectMessages/appendTimeline', { status: update, accountId: account.id }, { root: true })
|
||||
})
|
||||
win.ipcRenderer.removeAllListeners(`delete-direct-streamings-${account.id}`)
|
||||
win.ipcRenderer.on(`delete-direct-streamings-${account.id}`, (_, id: string) => {
|
||||
commit('TimelineSpace/Contents/DirectMessages/deleteToot', { statusId: id, accountId: account.id }, { root: true })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,63 +1,93 @@
|
|||
import { detector } from 'megalodon'
|
||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import { detector } from 'megalodon'
|
||||
import { RootState } from '@/store'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { LocalServer } from '~src/types/localServer'
|
||||
import { OAuth } from 'megalodon'
|
||||
import { LocalAccount } from '~src/types/localAccount'
|
||||
import { toRaw } from 'vue'
|
||||
|
||||
const win = window as any as MyWindow
|
||||
const win = (window as any) as MyWindow
|
||||
|
||||
export type LoginState = {
|
||||
selectedInstance: string | null
|
||||
domain: string | null
|
||||
searching: boolean
|
||||
server: LocalServer | null
|
||||
appData: OAuth.AppData | null
|
||||
sns: 'mastodon' | 'pleroma' | 'misskey'
|
||||
}
|
||||
|
||||
const state = (): LoginState => ({
|
||||
selectedInstance: null,
|
||||
domain: null,
|
||||
searching: false,
|
||||
server: null,
|
||||
appData: null,
|
||||
sns: 'mastodon'
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
CHANGE_INSTANCE: 'changeInstance',
|
||||
CHANGE_DOMAIN: 'changeDomain',
|
||||
CHANGE_SEARCHING: 'changeSearching',
|
||||
CHANGE_SERVER: 'changeServer',
|
||||
CHANGE_APP_DATA: 'changeAppData',
|
||||
CHANGE_SNS: 'changeSNS'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<LoginState> = {
|
||||
[MUTATION_TYPES.CHANGE_INSTANCE]: (state: LoginState, instance: string) => {
|
||||
state.selectedInstance = instance
|
||||
[MUTATION_TYPES.CHANGE_DOMAIN]: (state: LoginState, instance: string | null) => {
|
||||
state.domain = instance
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_SEARCHING]: (state: LoginState, searching: boolean) => {
|
||||
state.searching = searching
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_SERVER]: (state: LoginState, server: LocalServer | null) => {
|
||||
state.server = server
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_APP_DATA]: (state: LoginState, appData: OAuth.AppData | null) => {
|
||||
state.appData = appData
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_SNS]: (state: LoginState, sns: 'mastodon' | 'pleroma' | 'misskey') => {
|
||||
state.sns = sns
|
||||
}
|
||||
}
|
||||
|
||||
export const ACTION_TYPES = {
|
||||
FETCH_LOGIN: 'fetchLogin',
|
||||
ADD_SERVER: 'addServer',
|
||||
ADD_APP: 'addApp',
|
||||
AUTHORIZE: 'authorize',
|
||||
PAGE_BACK: 'pageBack',
|
||||
CONFIRM_INSTANCE: 'confirmInstance'
|
||||
}
|
||||
|
||||
const actions: ActionTree<LoginState, RootState> = {
|
||||
[ACTION_TYPES.FETCH_LOGIN]: async ({ state }): Promise<string> => {
|
||||
const url = await win.ipcRenderer.invoke('get-auth-url', {
|
||||
instance: state.selectedInstance,
|
||||
sns: state.sns
|
||||
[ACTION_TYPES.ADD_SERVER]: async ({ state, commit }): Promise<LocalServer> => {
|
||||
const server = await win.ipcRenderer.invoke('add-server', state.domain)
|
||||
commit(MUTATION_TYPES.CHANGE_SERVER, server)
|
||||
return server
|
||||
},
|
||||
[ACTION_TYPES.ADD_APP]: async ({ state, commit }) => {
|
||||
const appData = await win.ipcRenderer.invoke('add-app', `https://${state.domain}`)
|
||||
commit(MUTATION_TYPES.CHANGE_APP_DATA, appData)
|
||||
},
|
||||
[ACTION_TYPES.AUTHORIZE]: async ({ state }, code: string): Promise<number> => {
|
||||
const localAccount: LocalAccount = await win.ipcRenderer.invoke('authorize', {
|
||||
server: toRaw(state.server),
|
||||
appData: toRaw(state.appData),
|
||||
code
|
||||
})
|
||||
return url
|
||||
return localAccount.id
|
||||
},
|
||||
[ACTION_TYPES.PAGE_BACK]: ({ commit }) => {
|
||||
commit(MUTATION_TYPES.CHANGE_INSTANCE, null)
|
||||
commit(MUTATION_TYPES.CHANGE_DOMAIN, null)
|
||||
commit(MUTATION_TYPES.CHANGE_SERVER, null)
|
||||
commit(MUTATION_TYPES.CHANGE_APP_DATA, null)
|
||||
},
|
||||
[ACTION_TYPES.CONFIRM_INSTANCE]: async ({ commit }, domain: string): Promise<boolean> => {
|
||||
commit(MUTATION_TYPES.CHANGE_SEARCHING, true)
|
||||
const cleanDomain = domain.trim()
|
||||
try {
|
||||
const sns = await detector(`https://${cleanDomain}`)
|
||||
commit(MUTATION_TYPES.CHANGE_INSTANCE, cleanDomain)
|
||||
commit(MUTATION_TYPES.CHANGE_DOMAIN, cleanDomain)
|
||||
commit(MUTATION_TYPES.CHANGE_SNS, sns)
|
||||
} finally {
|
||||
commit(MUTATION_TYPES.CHANGE_SEARCHING, false)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import { toRaw } from 'vue'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { RootState } from '@/store'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { LocalServer } from '~src/types/localServer'
|
||||
|
||||
const win = window as any as MyWindow
|
||||
const win = (window as any) as MyWindow
|
||||
|
||||
export type AccountState = {
|
||||
accounts: Array<LocalAccount>
|
||||
accounts: Array<[LocalAccount, LocalServer]>
|
||||
accountLoading: boolean
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ export const MUTATION_TYPES = {
|
|||
}
|
||||
|
||||
const mutations: MutationTree<AccountState> = {
|
||||
[MUTATION_TYPES.UPDATE_ACCOUNTS]: (state, accounts: Array<LocalAccount>) => {
|
||||
[MUTATION_TYPES.UPDATE_ACCOUNTS]: (state, accounts: Array<[LocalAccount, LocalServer]>) => {
|
||||
state.accounts = accounts
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_ACCOUNT_LOADING]: (state, value: boolean) => {
|
||||
|
@ -39,19 +39,19 @@ export const ACTION_TYPES = {
|
|||
}
|
||||
|
||||
const actions: ActionTree<AccountState, RootState> = {
|
||||
[ACTION_TYPES.LOAD_ACCOUNTS]: async ({ commit }): Promise<Array<LocalAccount>> => {
|
||||
const accounts = await win.ipcRenderer.invoke('list-accounts')
|
||||
[ACTION_TYPES.LOAD_ACCOUNTS]: async ({ commit }): Promise<Array<[LocalAccount, LocalServer]>> => {
|
||||
const accounts: Array<[LocalAccount, LocalServer]> = await win.ipcRenderer.invoke('list-accounts')
|
||||
commit(MUTATION_TYPES.UPDATE_ACCOUNTS, accounts)
|
||||
return accounts
|
||||
},
|
||||
[ACTION_TYPES.REMOVE_ACCOUNT]: async (_, account: LocalAccount) => {
|
||||
await win.ipcRenderer.invoke('remove-account', account._id)
|
||||
[ACTION_TYPES.REMOVE_ACCOUNT]: async (_, id: number) => {
|
||||
await win.ipcRenderer.invoke('remove-account', id)
|
||||
},
|
||||
[ACTION_TYPES.FORWARD_ACCOUNT]: async (_, account: LocalAccount) => {
|
||||
await win.ipcRenderer.invoke('forward-account', toRaw(account))
|
||||
[ACTION_TYPES.FORWARD_ACCOUNT]: async (_, id: number) => {
|
||||
await win.ipcRenderer.invoke('forward-account', id)
|
||||
},
|
||||
[ACTION_TYPES.BACKWARD_ACCOUNT]: async (_, account: LocalAccount) => {
|
||||
await win.ipcRenderer.invoke('backward-account', toRaw(account))
|
||||
[ACTION_TYPES.BACKWARD_ACCOUNT]: async (_, id: number) => {
|
||||
await win.ipcRenderer.invoke('backward-account', id)
|
||||
},
|
||||
[ACTION_TYPES.REMOVE_ALL_ACCOUNTS]: async () => {
|
||||
await win.ipcRenderer.invoke('remove-all-accounts')
|
||||
|
|
|
@ -5,20 +5,20 @@ import { Module, MutationTree } from 'vuex'
|
|||
import { RootState } from '@/store'
|
||||
|
||||
export type SettingsState = {
|
||||
accountID: string | null
|
||||
accountId: number | null
|
||||
}
|
||||
|
||||
const state = (): SettingsState => ({
|
||||
accountID: null
|
||||
accountId: null
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
CHANGE_ACCOUNT_ID: 'changeAccountID'
|
||||
CHANGE_ACCOUNT_ID: 'changeAccountId'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<SettingsState> = {
|
||||
[MUTATION_TYPES.CHANGE_ACCOUNT_ID]: (state, id: string) => {
|
||||
state.accountID = id
|
||||
[MUTATION_TYPES.CHANGE_ACCOUNT_ID]: (state, id: number) => {
|
||||
state.accountId = id
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -36,9 +36,9 @@ export const ACTION_TYPES = {
|
|||
export const actions: ActionTree<FiltersState, RootState> = {
|
||||
[ACTION_TYPES.FETCH_FILTERS]: async ({ commit, rootState }): Promise<Array<Entity.Filter>> => {
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.sns,
|
||||
rootState.TimelineSpace.account.baseURL,
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
try {
|
||||
|
@ -52,9 +52,9 @@ export const actions: ActionTree<FiltersState, RootState> = {
|
|||
},
|
||||
[ACTION_TYPES.DELETE_FILTER]: async ({ commit, dispatch, rootState }, id: string) => {
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.sns,
|
||||
rootState.TimelineSpace.account.baseURL,
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
try {
|
||||
|
|
|
@ -42,9 +42,9 @@ export const ACTION_TYPES = {
|
|||
export const actions: ActionTree<EditFiltersState, RootState> = {
|
||||
fetchFilter: async ({ commit, rootState }, id: string): Promise<Entity.Filter> => {
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.sns,
|
||||
rootState.TimelineSpace.account.baseURL,
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
try {
|
||||
|
@ -65,9 +65,9 @@ export const actions: ActionTree<EditFiltersState, RootState> = {
|
|||
throw new Error('filter is not set')
|
||||
}
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.sns,
|
||||
rootState.TimelineSpace.account.baseURL,
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
try {
|
||||
|
|
|
@ -54,9 +54,9 @@ export const actions: ActionTree<NewFiltersState, RootState> = {
|
|||
throw new Error('filter is not set')
|
||||
}
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.sns,
|
||||
rootState.TimelineSpace.account.baseURL,
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
try {
|
||||
|
|
|
@ -36,9 +36,9 @@ export const ACTION_TYPES = {
|
|||
const actions: ActionTree<GeneralState, RootState> = {
|
||||
[ACTION_TYPES.FETCH_SETTINGS]: async ({ commit, rootState }): Promise<Entity.Account> => {
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.sns,
|
||||
rootState.TimelineSpace.account.baseURL,
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
const res = await client.verifyAccountCredentials()
|
||||
|
@ -51,9 +51,9 @@ const actions: ActionTree<GeneralState, RootState> = {
|
|||
},
|
||||
[ACTION_TYPES.SET_VISIBILITY]: async ({ commit, rootState }, value: number) => {
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.sns,
|
||||
rootState.TimelineSpace.account.baseURL,
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
const visibility: VisibilityType | undefined = (Object.values(Visibility) as Array<VisibilityType>).find(v => {
|
||||
|
@ -65,9 +65,9 @@ const actions: ActionTree<GeneralState, RootState> = {
|
|||
},
|
||||
[ACTION_TYPES.SET_SENSITIVE]: async ({ commit, rootState }, value: boolean) => {
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.sns,
|
||||
rootState.TimelineSpace.account.baseURL,
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
const res = await client.updateCredentials({ source: { sensitive: value } })
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||
import { toRaw } from 'vue'
|
||||
import { RootState } from '@/store'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { Setting, UnreadNotification, Timeline as TimelineSetting, UseMarker } from '~src/types/setting'
|
||||
import { Setting } from '~src/types/setting'
|
||||
import { DefaultSetting } from '~/src/constants/initializer/setting'
|
||||
|
||||
const win = window as any as MyWindow
|
||||
const win = (window as any) as MyWindow
|
||||
|
||||
export type TimelineState = {
|
||||
setting: TimelineSetting
|
||||
setting: Setting
|
||||
}
|
||||
|
||||
const state = (): TimelineState => ({
|
||||
setting: DefaultSetting.timeline
|
||||
setting: DefaultSetting
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
|
@ -20,7 +19,7 @@ export const MUTATION_TYPES = {
|
|||
}
|
||||
|
||||
const mutations: MutationTree<TimelineState> = {
|
||||
[MUTATION_TYPES.UPDATE_TIMELINE_SETTING]: (state, setting: TimelineSetting) => {
|
||||
[MUTATION_TYPES.UPDATE_TIMELINE_SETTING]: (state, setting: Setting) => {
|
||||
state.setting = setting
|
||||
}
|
||||
}
|
||||
|
@ -33,34 +32,16 @@ export const ACTION_TYPES = {
|
|||
|
||||
const actions: ActionTree<TimelineState, RootState> = {
|
||||
[ACTION_TYPES.LOAD_TIMELINE_SETTING]: async ({ commit, rootState }): Promise<boolean> => {
|
||||
const setting: Setting = await win.ipcRenderer.invoke('get-account-setting', rootState.Settings.accountID)
|
||||
commit(MUTATION_TYPES.UPDATE_TIMELINE_SETTING, setting.timeline)
|
||||
return true
|
||||
},
|
||||
[ACTION_TYPES.CHANGE_UNREAD_NOTIFICATION]: async ({ dispatch, state, rootState }, timeline: { key: boolean }): Promise<boolean> => {
|
||||
const unread: UnreadNotification = Object.assign({}, state.setting.unreadNotification, timeline)
|
||||
const tl: TimelineSetting = Object.assign({}, toRaw(state.setting), {
|
||||
unreadNotification: unread
|
||||
})
|
||||
const setting: Setting = {
|
||||
accountID: rootState.Settings.accountID!,
|
||||
timeline: tl
|
||||
}
|
||||
await win.ipcRenderer.invoke('update-account-setting', setting)
|
||||
dispatch('loadTimelineSetting')
|
||||
const setting: Setting = await win.ipcRenderer.invoke('get-account-setting', rootState.Settings.accountId)
|
||||
commit(MUTATION_TYPES.UPDATE_TIMELINE_SETTING, setting)
|
||||
return true
|
||||
},
|
||||
[ACTION_TYPES.CHANGE_USER_MARKER]: async ({ dispatch, state, rootState }, timeline: { key: boolean }) => {
|
||||
const marker: UseMarker = Object.assign({}, state.setting.useMarker, timeline)
|
||||
const tl: TimelineSetting = Object.assign({}, toRaw(state.setting), {
|
||||
useMarker: marker
|
||||
})
|
||||
const setting: Setting = {
|
||||
accountID: rootState.Settings.accountID!,
|
||||
timeline: tl
|
||||
}
|
||||
const setting: Setting = Object.assign({}, state.setting, timeline)
|
||||
setting.accountId = rootState.Settings.accountId!
|
||||
console.log(setting)
|
||||
await win.ipcRenderer.invoke('update-account-setting', setting)
|
||||
dispatch('loadTimelineSetting')
|
||||
dispatch(ACTION_TYPES.LOAD_TIMELINE_SETTING)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue