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",
|
"rc": "^1.2.7",
|
||||||
"sanitize-html": "^2.8.1",
|
"sanitize-html": "^2.8.1",
|
||||||
"simplayer": "0.0.8",
|
"simplayer": "0.0.8",
|
||||||
|
"sqlite3": "^5.1.4",
|
||||||
"system-font-families": "^0.6.0",
|
"system-font-families": "^0.6.0",
|
||||||
"tunnel-agent": "^0.6.0",
|
"tunnel-agent": "^0.6.0",
|
||||||
"unicode-emoji-json": "^0.4.0",
|
"unicode-emoji-json": "^0.4.0",
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { createStore, Store } from 'vuex'
|
||||||
import { ipcMain, ipcRenderer } from '~/spec/mock/electron'
|
import { ipcMain, ipcRenderer } from '~/spec/mock/electron'
|
||||||
import GlobalHeader, { GlobalHeaderState } from '~/src/renderer/store/GlobalHeader'
|
import GlobalHeader, { GlobalHeaderState } from '~/src/renderer/store/GlobalHeader'
|
||||||
import { MyWindow } from '~/src/types/global'
|
import { MyWindow } from '~/src/types/global'
|
||||||
;(window as any as MyWindow).ipcRenderer = ipcRenderer
|
;((window as any) as MyWindow).ipcRenderer = ipcRenderer
|
||||||
|
|
||||||
const state = (): GlobalHeaderState => {
|
const state = (): GlobalHeaderState => {
|
||||||
return {
|
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', () => {
|
describe('removeShortcutEvents', () => {
|
||||||
it('should be removed', async () => {
|
it('should be removed', async () => {
|
||||||
const removed = await store.dispatch('GlobalHeader/removeShortcutEvents')
|
const removed = await store.dispatch('GlobalHeader/removeShortcutEvents')
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { createStore, Store } from 'vuex'
|
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 Login, { LoginState } from '@/store/Login'
|
||||||
import { MyWindow } from '~/src/types/global'
|
import { MyWindow } from '~/src/types/global'
|
||||||
import { RootState } from '@/store'
|
import { RootState } from '@/store'
|
||||||
;(window as any as MyWindow).ipcRenderer = ipcRenderer
|
;((window as any) as MyWindow).ipcRenderer = ipcRenderer
|
||||||
|
|
||||||
jest.mock('megalodon', () => ({
|
jest.mock('megalodon', () => ({
|
||||||
...jest.requireActual<object>('megalodon'),
|
...jest.requireActual<object>('megalodon'),
|
||||||
|
@ -13,8 +13,10 @@ jest.mock('megalodon', () => ({
|
||||||
|
|
||||||
const state = (): LoginState => {
|
const state = (): LoginState => {
|
||||||
return {
|
return {
|
||||||
selectedInstance: null,
|
domain: null,
|
||||||
searching: false,
|
searching: false,
|
||||||
|
server: null,
|
||||||
|
appData: null,
|
||||||
sns: 'mastodon'
|
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', () => {
|
describe('pageBack', () => {
|
||||||
it('should reset instance', () => {
|
it('should reset instance', () => {
|
||||||
store.dispatch('Login/pageBack')
|
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 () => {
|
it('should change instance', async () => {
|
||||||
const result = await store.dispatch('Login/confirmInstance', 'pleroma.io')
|
const result = await store.dispatch('Login/confirmInstance', 'pleroma.io')
|
||||||
expect(result).toEqual(true)
|
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,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
account: {
|
account: {
|
||||||
baseURL: 'https://example.com',
|
|
||||||
domain: 'example.com',
|
|
||||||
clientId: 'sampleId',
|
|
||||||
clientSecret: 'sampleSecret',
|
|
||||||
accessToken: 'sampleAccessToken',
|
accessToken: 'sampleAccessToken',
|
||||||
refreshToken: null,
|
id: 1,
|
||||||
username: 'h3poteto',
|
username: 'h3poteto'
|
||||||
accountID: '1',
|
},
|
||||||
avatar: null,
|
server: {
|
||||||
order: 1
|
sns: 'mastodon',
|
||||||
|
baseURL: 'https://example.com'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modules: {
|
modules: {
|
||||||
|
|
|
@ -49,10 +49,12 @@ const timelineStore = () => ({
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
account: {
|
account: {
|
||||||
accessToken: 'token',
|
accessToken: 'token'
|
||||||
baseURL: 'http://localhost'
|
|
||||||
},
|
},
|
||||||
sns: 'mastodon'
|
server: {
|
||||||
|
sns: 'mastodon',
|
||||||
|
baseURL: 'http://localhost'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modules: {
|
modules: {
|
||||||
HeaderMenu: initStore()
|
HeaderMenu: initStore()
|
||||||
|
|
|
@ -84,9 +84,12 @@ const timelineStore = () => ({
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
account: {
|
account: {
|
||||||
_id: '0'
|
id: 0,
|
||||||
|
accessToken: 'token'
|
||||||
},
|
},
|
||||||
sns: 'mastodon'
|
server: {
|
||||||
|
sns: 'mastodon'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
modules: {
|
modules: {
|
||||||
Modals: modalsStore()
|
Modals: modalsStore()
|
||||||
|
|
|
@ -42,8 +42,6 @@ const state = (): JumpState => {
|
||||||
path: 'direct-messages'
|
path: 'direct-messages'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
listChannelList: [],
|
|
||||||
tagChannelList: [],
|
|
||||||
selectedChannel: {
|
selectedChannel: {
|
||||||
name: i18n.t('side_menu.home'),
|
name: i18n.t('side_menu.home'),
|
||||||
path: 'home'
|
path: 'home'
|
||||||
|
@ -70,7 +68,7 @@ const timelineStore = () => ({
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
account: {
|
account: {
|
||||||
_id: '0'
|
id: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modules: {
|
modules: {
|
||||||
|
|
|
@ -130,7 +130,12 @@ const timelineStore = () => ({
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: {
|
state: {
|
||||||
account: {
|
account: {
|
||||||
_id: '0'
|
id: 0,
|
||||||
|
accessToken: 'token'
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
sns: 'mastodon',
|
||||||
|
baseURL: 'http://localhost'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modules: {
|
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
|
let state: LoginState
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
state = {
|
state = {
|
||||||
selectedInstance: null,
|
domain: null,
|
||||||
searching: false,
|
searching: false,
|
||||||
|
server: null,
|
||||||
|
appData: null,
|
||||||
sns: 'mastodon'
|
sns: 'mastodon'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
describe('changeInstance', () => {
|
describe('changeInstance', () => {
|
||||||
it('should be changed', () => {
|
it('should be changed', () => {
|
||||||
Login.mutations![MUTATION_TYPES.CHANGE_INSTANCE](state, 'pleroma.io')
|
Login.mutations![MUTATION_TYPES.CHANGE_DOMAIN](state, 'pleroma.io')
|
||||||
expect(state.selectedInstance).toEqual('pleroma.io')
|
expect(state.domain).toEqual('pleroma.io')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('changeSearching', () => {
|
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'
|
import { DefaultSetting } from '~/src/constants/initializer/setting'
|
||||||
|
|
||||||
describe('TimelineSpace', () => {
|
describe('TimelineSpace', () => {
|
||||||
|
@ -6,13 +6,12 @@ describe('TimelineSpace', () => {
|
||||||
let state: TimelineSpaceState
|
let state: TimelineSpaceState
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
state = {
|
state = {
|
||||||
account: blankAccount,
|
account: null,
|
||||||
bindingAccount: null,
|
server: null,
|
||||||
loading: false,
|
loading: false,
|
||||||
emojis: [],
|
emojis: [],
|
||||||
tootMax: 500,
|
tootMax: 500,
|
||||||
timelineSetting: DefaultSetting.timeline,
|
setting: DefaultSetting,
|
||||||
sns: 'mastodon',
|
|
||||||
filters: []
|
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",
|
"expand": "Expand",
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"notification": "Notification",
|
"notification": "Notification",
|
||||||
"mention": "Mention",
|
|
||||||
"direct": "Direct messages",
|
"direct": "Direct messages",
|
||||||
"follow_requests": "Follow Requests",
|
"follow_requests": "Follow Requests",
|
||||||
"favourite": "Favourite",
|
"favourite": "Favourite",
|
||||||
|
@ -64,7 +63,6 @@
|
||||||
"header_menu": {
|
"header_menu": {
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"notification": "Notification",
|
"notification": "Notification",
|
||||||
"mention": "Mention",
|
|
||||||
"favourite": "Favourite",
|
"favourite": "Favourite",
|
||||||
"bookmark": "Bookmark",
|
"bookmark": "Bookmark",
|
||||||
"follow_requests": "Follow Requests",
|
"follow_requests": "Follow Requests",
|
||||||
|
@ -463,10 +461,9 @@
|
||||||
"timeline_fetch_error": "Failed to fetch timeline",
|
"timeline_fetch_error": "Failed to fetch timeline",
|
||||||
"notification_fetch_error": "Failed to fetch notification",
|
"notification_fetch_error": "Failed to fetch notification",
|
||||||
"favourite_fetch_error": "Failed to fetch favorite",
|
"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_accept_error": "Failed to accept the request",
|
||||||
"follow_request_reject_error": "Failed to reject 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",
|
"attach_error": "Could not attach the file",
|
||||||
"authorize_duplicate_error": "Can not login the same account of the same domain",
|
"authorize_duplicate_error": "Can not login the same account of the same domain",
|
||||||
"authorize_error": "Failed to authorize",
|
"authorize_error": "Failed to authorize",
|
||||||
|
|
|
@ -1,22 +1,7 @@
|
||||||
import { Setting, Timeline, UnreadNotification, UseMarker } from '~/src/types/setting'
|
import { Setting } 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
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DefaultSetting: Setting = {
|
export const DefaultSetting: Setting = {
|
||||||
accountID: '',
|
accountId: 0,
|
||||||
timeline: timeline
|
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> => {
|
const newDB = (file: string): sqlite3.Database => {
|
||||||
return new Promise(resolve => {
|
const db = new sqlite3.Database(file, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE)
|
||||||
const databaseInitializer = () => {
|
|
||||||
let markers = db.getCollection('markers')
|
// migration
|
||||||
if (markers === null) {
|
db.serialize(() => {
|
||||||
markers = db.addCollection('markers')
|
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)
|
)
|
||||||
}
|
db.run(
|
||||||
|
'CREATE TABLE IF NOT EXISTS servers(\
|
||||||
const db = new Loki(file, {
|
id INTEGER PRIMARY KEY, \
|
||||||
autoload: true,
|
domain TEXT NOT NULL, \
|
||||||
autosave: true,
|
base_url TEXT NOT NULL, \
|
||||||
autosaveInterval: 4000,
|
sns TEXT NOT NULL, \
|
||||||
autoloadCallback: databaseInitializer
|
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
|
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 generator, { MegalodonInterface, WebSocketInterface, Entity, ProxyConfig } from 'megalodon'
|
||||||
import log from 'electron-log'
|
import log from 'electron-log'
|
||||||
import { LocalAccount } from '~/src/types/localAccount'
|
import { LocalAccount } from '~/src/types/localAccount'
|
||||||
|
import { LocalServer } from '~src/types/localServer'
|
||||||
|
|
||||||
const StreamingURL = async (
|
const StreamingURL = async (
|
||||||
sns: 'mastodon' | 'pleroma' | 'misskey',
|
sns: 'mastodon' | 'pleroma' | 'misskey',
|
||||||
account: LocalAccount,
|
account: LocalAccount,
|
||||||
|
server: LocalServer,
|
||||||
proxy: ProxyConfig | false
|
proxy: ProxyConfig | false
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
if (!account.accessToken) {
|
if (!account.accessToken) {
|
||||||
throw new Error('access token is empty')
|
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()
|
const res = await client.getInstance()
|
||||||
return res.data.urls.streaming_api
|
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"
|
role="menubar"
|
||||||
>
|
>
|
||||||
<el-menu-item
|
<el-menu-item
|
||||||
:index="`/${account._id}/`"
|
:index="`/${account.id}/`"
|
||||||
:route="{ path: `/${account._id}/home` }"
|
:route="{ path: `/${account.id}/home` }"
|
||||||
v-for="(account, _index) in accounts"
|
v-for="([account, server], _index) in accounts"
|
||||||
v-bind:key="account._id"
|
:key="account.id"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
>
|
>
|
||||||
<font-awesome-icon icon="circle-user" v-if="account.avatar === undefined || account.avatar === null || account.avatar === ''" />
|
<FailoverImg :src="account.avatar" class="avatar" :title="account.username + '@' + server.domain" />
|
||||||
<FailoverImg v-else :src="account.avatar" class="avatar" :title="account.username + '@' + account.domain" />
|
<FailoverImg :src="`${server.baseURL}/favicon.ico`" :failoverSrc="`${server.baseURL}/favicon.png`" class="instance-icon" />
|
||||||
<FailoverImg :src="`${account.baseURL}/favicon.ico`" :failoverSrc="`${account.baseURL}/favicon.png`" class="instance-icon" />
|
<span slot="title">{{ server.domain }}</span>
|
||||||
<span slot="title">{{ account.domain }}</span>
|
|
||||||
</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item index="/login" :title="$t('global_header.add_new_account')" role="menuitem" class="add-new-account">
|
<el-menu-item index="/login" :title="$t('global_header.add_new_account')" role="menuitem" class="add-new-account">
|
||||||
<font-awesome-icon icon="plus" />
|
<font-awesome-icon icon="plus" />
|
||||||
|
@ -37,11 +36,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, computed, onMounted } from 'vue'
|
import { defineComponent, computed, onMounted } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import { useI18next } from 'vue3-i18next'
|
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
import FailoverImg from '@/components/atoms/FailoverImg.vue'
|
import FailoverImg from '@/components/atoms/FailoverImg.vue'
|
||||||
import { StreamingError } from '~/src/errors/streamingError'
|
|
||||||
import { ACTION_TYPES } from '@/store/GlobalHeader'
|
import { ACTION_TYPES } from '@/store/GlobalHeader'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -54,7 +50,6 @@ export default defineComponent({
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const i18n = useI18next()
|
|
||||||
|
|
||||||
const accounts = computed(() => store.state.GlobalHeader.accounts)
|
const accounts = computed(() => store.state.GlobalHeader.accounts)
|
||||||
const hide = computed(() => store.state.GlobalHeader.hide)
|
const hide = computed(() => store.state.GlobalHeader.hide)
|
||||||
|
@ -67,20 +62,10 @@ export default defineComponent({
|
||||||
|
|
||||||
const initialize = async () => {
|
const initialize = async () => {
|
||||||
await store
|
await store
|
||||||
.dispatch(`${space}/initLoad`)
|
.dispatch(`${space}/${ACTION_TYPES.INIT_LOAD}`)
|
||||||
.then(accounts => {
|
.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) {
|
if (route.params.id === undefined) {
|
||||||
router.push({ path: `/${accounts[0]._id}/home` })
|
router.push({ path: `/${accounts[0][0].id}/home` })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(_ => {
|
.catch(_ => {
|
||||||
|
|
|
@ -10,29 +10,32 @@
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-header>
|
</el-header>
|
||||||
<el-container>
|
<el-container>
|
||||||
<div></div>
|
<login-form v-if="appData === null" />
|
||||||
<login-form></login-form>
|
<authorize v-else />
|
||||||
</el-container>
|
</el-container>
|
||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||||
import LoginForm from './Login/LoginForm.vue'
|
import LoginForm from './Login/LoginForm.vue'
|
||||||
|
import Authorize from './Login/Authorize.vue'
|
||||||
import { ACTION_TYPES } from '@/store/Login'
|
import { ACTION_TYPES } from '@/store/Login'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'login',
|
name: 'login',
|
||||||
components: { LoginForm },
|
components: { LoginForm, Authorize },
|
||||||
setup() {
|
setup() {
|
||||||
const space = 'Login'
|
const space = 'Login'
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { escape } = useMagicKeys()
|
const { escape } = useMagicKeys()
|
||||||
|
|
||||||
|
const appData = computed(() => store.state.Login.appData)
|
||||||
|
|
||||||
whenever(escape, () => {
|
whenever(escape, () => {
|
||||||
close()
|
close()
|
||||||
})
|
})
|
||||||
|
@ -46,7 +49,8 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
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 { defineComponent, computed, reactive, ref } from 'vue'
|
||||||
import { useI18next } from 'vue3-i18next'
|
import { useI18next } from 'vue3-i18next'
|
||||||
import { ElLoading, ElMessage, FormInstance, FormRules } from 'element-plus'
|
import { ElLoading, ElMessage, FormInstance, FormRules } from 'element-plus'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
import { domainFormat } from '@/utils/validator'
|
import { domainFormat } from '@/utils/validator'
|
||||||
import { ACTION_TYPES } from '@/store/Login'
|
import { ACTION_TYPES } from '@/store/Login'
|
||||||
|
@ -51,16 +50,14 @@ export default defineComponent({
|
||||||
const space = 'Login'
|
const space = 'Login'
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const i18n = useI18next()
|
const i18n = useI18next()
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
domainName: ''
|
domainName: ''
|
||||||
})
|
})
|
||||||
const loginFormRef = ref<FormInstance>()
|
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 searching = computed(() => store.state.Login.searching)
|
||||||
const sns = computed(() => store.state.Login.sns)
|
|
||||||
const allowLogin = computed(() => selectedInstance.value && form.domainName === selectedInstance.value)
|
const allowLogin = computed(() => selectedInstance.value && form.domainName === selectedInstance.value)
|
||||||
const rules = reactive<FormRules>({
|
const rules = reactive<FormRules>({
|
||||||
domainName: [
|
domainName: [
|
||||||
|
@ -77,31 +74,24 @@ export default defineComponent({
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const login = () => {
|
const login = async () => {
|
||||||
const loading = ElLoading.service({
|
const loading = ElLoading.service({
|
||||||
lock: true,
|
lock: true,
|
||||||
text: i18n.t('message.loading'),
|
text: i18n.t('message.loading'),
|
||||||
background: 'rgba(0, 0, 0, 0.7)'
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
})
|
})
|
||||||
store
|
try {
|
||||||
.dispatch(`${space}/${ACTION_TYPES.FETCH_LOGIN}`)
|
await store.dispatch(`${space}/${ACTION_TYPES.ADD_SERVER}`)
|
||||||
.then(url => {
|
await store.dispatch(`${space}/${ACTION_TYPES.ADD_APP}`)
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.PAGE_BACK}`)
|
} catch (err) {
|
||||||
router.push({
|
ElMessage({
|
||||||
path: '/authorize',
|
message: i18n.t('message.authorize_url_error'),
|
||||||
query: { url: url, sns: sns.value }
|
type: 'error'
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
ElMessage({
|
|
||||||
message: i18n.t('message.authorize_url_error'),
|
|
||||||
type: 'error'
|
|
||||||
})
|
|
||||||
console.error(err)
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loading.close()
|
|
||||||
})
|
})
|
||||||
|
console.error(err)
|
||||||
|
} finally {
|
||||||
|
loading.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirm = async (formEl: FormInstance | undefined) => {
|
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 prop="domain" :label="$t('preferences.account.domain')"> </el-table-column>
|
||||||
<el-table-column :label="$t('preferences.account.association')">
|
<el-table-column :label="$t('preferences.account.association')">
|
||||||
<template #default="scope">
|
<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" />
|
<font-awesome-icon icon="xmark" />
|
||||||
{{ $t('preferences.account.remove_association') }}
|
{{ $t('preferences.account.remove_association') }}
|
||||||
</el-button>
|
</el-button>
|
||||||
|
@ -25,12 +34,30 @@
|
||||||
<el-table-column :label="$t('preferences.account.order')" width="60">
|
<el-table-column :label="$t('preferences.account.order')" width="60">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="allow-up">
|
<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" />
|
<font-awesome-icon icon="arrow-up" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="allow-down">
|
<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" />
|
<font-awesome-icon icon="arrow-down" />
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,7 +88,6 @@ import { useRouter } from 'vue-router'
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/Preferences/Account'
|
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/Preferences/Account'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { LocalAccount } from '~/src/types/localAccount'
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'account',
|
name: 'account',
|
||||||
|
@ -71,7 +97,13 @@ export default defineComponent({
|
||||||
const i18n = useI18next()
|
const i18n = useI18next()
|
||||||
const router = useRouter()
|
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 accountLoading = computed(() => store.state.Preferences.Account.accountLoading)
|
||||||
const backgroundColor = computed(() => store.state.App.theme.background_color)
|
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
|
store
|
||||||
.dispatch(`${space}/${ACTION_TYPES.REMOVE_ACCOUNT}`, accounts[index])
|
.dispatch(`${space}/${ACTION_TYPES.REMOVE_ACCOUNT}`, accounts[index])
|
||||||
.then(() => {
|
.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(() => {
|
store.dispatch(`${space}/${ACTION_TYPES.FORWARD_ACCOUNT}`, accounts[index]).then(() => {
|
||||||
loadAccounts()
|
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(() => {
|
store.dispatch(`${space}/${ACTION_TYPES.BACKWARD_ACCOUNT}`, accounts[index]).then(() => {
|
||||||
loadAccounts()
|
loadAccounts()
|
||||||
})
|
})
|
||||||
|
|
|
@ -64,7 +64,7 @@ export default defineComponent({
|
||||||
const activeRoute = computed(() => route.path)
|
const activeRoute = computed(() => route.path)
|
||||||
|
|
||||||
onMounted(() => {
|
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`)
|
router.push(`/${id.value}/settings/general`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="timeline">
|
<div id="timeline">
|
||||||
<h2>{{ $t('settings.timeline.title') }}</h2>
|
<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">
|
<el-form class="use-marker section" size="default" label-position="right" label-width="250px">
|
||||||
<h3>{{ $t('settings.timeline.use_marker.title') }}</h3>
|
<h3>{{ $t('settings.timeline.use_marker.title') }}</h3>
|
||||||
<el-form-item for="marker_home" :label="$t('settings.timeline.use_marker.home')">
|
<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 space = 'Settings/Timeline'
|
||||||
const store = useStore()
|
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({
|
const marker_home = computed({
|
||||||
get: () => store.state.Settings.Timeline.setting.useMarker.home,
|
get: () => store.state.Settings.Timeline.setting.markerHome,
|
||||||
set: value =>
|
set: value =>
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_USER_MARKER}`, {
|
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_USER_MARKER}`, {
|
||||||
home: value
|
markerHome: value
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const marker_notifications = computed({
|
const marker_notifications = computed({
|
||||||
get: () => store.state.Settings.Timeline.setting.useMarker.notifications,
|
get: () => store.state.Settings.Timeline.setting.markerNotifications,
|
||||||
set: value =>
|
set: value =>
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_USER_MARKER}`, {
|
store.dispatch(`${space}/${ACTION_TYPES.CHANGE_USER_MARKER}`, {
|
||||||
notifications: value
|
markerNotifications: value
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -82,9 +44,6 @@ export default defineComponent({
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
directNotify,
|
|
||||||
localNotify,
|
|
||||||
publicNotify,
|
|
||||||
marker_home,
|
marker_home,
|
||||||
marker_notifications
|
marker_notifications
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,14 +75,10 @@ export default defineComponent({
|
||||||
;(window as any).removeEventListener('dragleave', onDragLeave)
|
;(window as any).removeEventListener('dragleave', onDragLeave)
|
||||||
;(window as any).removeEventListener('dragover', onDragOver)
|
;(window as any).removeEventListener('dragover', onDragOver)
|
||||||
;(window as any).removeEventListener('drop', handleDrop)
|
;(window as any).removeEventListener('drop', handleDrop)
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.STOP_STREAMINGS}`)
|
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.UNBIND_STREAMINGS}`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const clear = async () => {
|
const clear = async () => {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.UNBIND_STREAMINGS}`)
|
|
||||||
await store.dispatch(`${space}/${ACTION_TYPES.CLEAR_ACCOUNT}`)
|
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.REMOVE_SHORTCUT_EVENTS}`)
|
||||||
await store.dispatch(`${space}/${ACTION_TYPES.CLEAR_UNREAD}`)
|
await store.dispatch(`${space}/${ACTION_TYPES.CLEAR_UNREAD}`)
|
||||||
return 'clear'
|
return 'clear'
|
||||||
|
|
|
@ -5,10 +5,13 @@
|
||||||
<template v-slot="{ item, index, active }">
|
<template v-slot="{ item, index, active }">
|
||||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||||
<toot
|
<toot
|
||||||
|
v-if="account.account && account.server"
|
||||||
:message="item"
|
:message="item"
|
||||||
:focused="item.uri === focusedId"
|
:focused="item.uri === focusedId"
|
||||||
:overlaid="modalOpened"
|
:overlaid="modalOpened"
|
||||||
:filters="[]"
|
:filters="[]"
|
||||||
|
:account="account.account"
|
||||||
|
:server="account.server"
|
||||||
v-on:update="updateToot"
|
v-on:update="updateToot"
|
||||||
v-on:delete="deleteToot"
|
v-on:delete="deleteToot"
|
||||||
@focusRight="focusSidebar"
|
@focusRight="focusSidebar"
|
||||||
|
@ -26,7 +29,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { logicAnd } from '@vueuse/math'
|
||||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
|
@ -40,6 +43,9 @@ import { EventEmitter } from '@/components/event'
|
||||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Bookmarks'
|
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Bookmarks'
|
||||||
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
||||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
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({
|
export default defineComponent({
|
||||||
name: 'bookmarks',
|
name: 'bookmarks',
|
||||||
|
@ -54,22 +60,33 @@ export default defineComponent({
|
||||||
const focusedId = ref<string | null>(null)
|
const focusedId = ref<string | null>(null)
|
||||||
const heading = ref<boolean>(true)
|
const heading = ref<boolean>(true)
|
||||||
const scroller = ref<any>()
|
const scroller = ref<any>()
|
||||||
|
const lazyLoading = ref(false)
|
||||||
const { j, k, Ctrl_r } = useMagicKeys()
|
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 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 startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||||
const currentFocusedIndex = computed(() => bookmarks.value.findIndex(toot => focusedId.value === toot.uri))
|
const currentFocusedIndex = computed(() => bookmarks.value.findIndex(toot => focusedId.value === toot.uri))
|
||||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
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)
|
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||||
store.commit(`TimelineSpace/Contents/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
store.commit(`TimelineSpace/Contents/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||||
store
|
store
|
||||||
.dispatch(`${space}/${ACTION_TYPES.FETCH_BOOKMARKS}`, account.value)
|
.dispatch(`${space}/${ACTION_TYPES.FETCH_BOOKMARKS}`, account)
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: i18n.t('message.bookmark_fetch_error'),
|
message: i18n.t('message.bookmark_fetch_error'),
|
||||||
|
@ -108,12 +125,18 @@ export default defineComponent({
|
||||||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||||
!lazyLoading.value
|
!lazyLoading.value
|
||||||
) {
|
) {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_BOOKMARKS}`, bookmarks.value[bookmarks.value.length - 1]).catch(() => {
|
lazyLoading.value = true
|
||||||
ElMessage({
|
store
|
||||||
message: i18n.t('message.bookmark_fetch_error'),
|
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_BOOKMARKS}`, account)
|
||||||
type: 'error'
|
.catch(() => {
|
||||||
|
ElMessage({
|
||||||
|
message: i18n.t('message.bookmark_fetch_error'),
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
lazyLoading.value = false
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
// for upper
|
// for upper
|
||||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||||
|
@ -126,7 +149,7 @@ export default defineComponent({
|
||||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||||
try {
|
try {
|
||||||
await reloadable()
|
await reloadable()
|
||||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_BOOKMARKS}`, account.value).catch(() => {
|
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_BOOKMARKS}`, account).catch(() => {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: i18n.t('message.bookmark_fetch_error'),
|
message: i18n.t('message.bookmark_fetch_error'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
|
@ -178,7 +201,8 @@ export default defineComponent({
|
||||||
focusToot,
|
focusToot,
|
||||||
openSideBar,
|
openSideBar,
|
||||||
heading,
|
heading,
|
||||||
upper
|
upper,
|
||||||
|
account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="directmessages">
|
<div id="directmessages">
|
||||||
<div></div>
|
|
||||||
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||||
<template v-slot="{ item, index, active }">
|
<template v-slot="{ item, index, active }">
|
||||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||||
<toot
|
<toot
|
||||||
|
v-if="account.account && account.server"
|
||||||
:message="item"
|
:message="item"
|
||||||
:focused="item.uri + item.id === focusedId"
|
:focused="item.uri + item.id === focusedId"
|
||||||
:overlaid="modalOpened"
|
:overlaid="modalOpened"
|
||||||
:filters="[]"
|
:filters="[]"
|
||||||
|
:account="account.account"
|
||||||
|
:server="account.server"
|
||||||
v-on:update="updateToot"
|
v-on:update="updateToot"
|
||||||
v-on:delete="deleteToot"
|
v-on:delete="deleteToot"
|
||||||
@focusRight="focusSidebar"
|
@focusRight="focusSidebar"
|
||||||
|
@ -27,7 +29,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { logicAnd } from '@vueuse/math'
|
||||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
|
@ -35,14 +37,13 @@ import { useI18next } from 'vue3-i18next'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { Entity } from 'megalodon'
|
import { Entity } from 'megalodon'
|
||||||
import useReloadable from '@/components/utils/reloadable'
|
|
||||||
import Toot from '@/components/organisms/Toot.vue'
|
import Toot from '@/components/organisms/Toot.vue'
|
||||||
import { EventEmitter } from '@/components/event'
|
import { EventEmitter } from '@/components/event'
|
||||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/DirectMessages'
|
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 SIDE_MENU_MUTATION } from '@/store/TimelineSpace/SideMenu'
|
||||||
import { MUTATION_TYPES as TIMELINE_MUTATION, ACTION_TYPES as TIMELINE_ACTION } from '@/store/TimelineSpace'
|
import { LocalAccount } from '~/src/types/localAccount'
|
||||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
import { LocalServer } from '~/src/types/localServer'
|
||||||
import { ACTION_TYPES as CONTENTS_ACTION } from '@/store/TimelineSpace/Contents'
|
import { MyWindow } from '~/src/types/global'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'directmessages',
|
name: 'directmessages',
|
||||||
|
@ -52,31 +53,33 @@ export default defineComponent({
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const i18n = useI18next()
|
const i18n = useI18next()
|
||||||
const { reloadable } = useReloadable(store, route, i18n)
|
const { j, k } = useMagicKeys()
|
||||||
const { j, k, Ctrl_r } = useMagicKeys()
|
|
||||||
|
const win = (window as any) as MyWindow
|
||||||
|
const id = computed(() => parseInt(route.params.id as string))
|
||||||
|
|
||||||
const focusedId = ref<string | null>(null)
|
const focusedId = ref<string | null>(null)
|
||||||
const scroller = ref<any>()
|
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 timeline = computed(() => store.state.TimelineSpace.Contents.DirectMessages.timeline[id.value])
|
||||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.DirectMessages.lazyLoading)
|
|
||||||
const heading = computed(() => store.state.TimelineSpace.Contents.DirectMessages.heading)
|
|
||||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
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 modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||||
|
|
||||||
onMounted(async () => {
|
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)
|
store.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_HOME_TIMELINE}`, false)
|
||||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
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(() => {
|
onBeforeUpdate(() => {
|
||||||
if (store.state.TimelineSpace.SideMenu.unreadDirectMessagesTimeline && heading.value) {
|
if (store.state.TimelineSpace.SideMenu.unreadDirectMessagesTimeline && heading.value) {
|
||||||
|
@ -84,30 +87,13 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (!unreadNotification.value.direct) {
|
EventEmitter.off('focus-timeline')
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
watch(focusedId, (newVal, _oldVal) => {
|
watch(focusedId, (newVal, _oldVal) => {
|
||||||
if (newVal && heading.value) {
|
if (newVal && heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
heading.value = false
|
||||||
} else if (newVal === null && !heading.value) {
|
} else if (newVal === null && !heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||||
|
@ -120,20 +106,7 @@ export default defineComponent({
|
||||||
whenever(logicAnd(k, shortcutEnabled), () => {
|
whenever(logicAnd(k, shortcutEnabled), () => {
|
||||||
focusPrev()
|
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) => {
|
const onScroll = (event: Event) => {
|
||||||
// for lazyLoading
|
// for lazyLoading
|
||||||
if (
|
if (
|
||||||
|
@ -141,32 +114,38 @@ export default defineComponent({
|
||||||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||||
!lazyLoading.value
|
!lazyLoading.value
|
||||||
) {
|
) {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, timeline.value[timeline.value.length - 1]).catch(() => {
|
lazyLoading.value = true
|
||||||
ElMessage({
|
store
|
||||||
message: i18n.t('message.timeline_fetch_error'),
|
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||||
type: 'error'
|
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) {
|
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) {
|
} 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) => {
|
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) => {
|
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 reload = async () => {
|
|
||||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
|
||||||
try {
|
|
||||||
await reloadable()
|
|
||||||
} finally {
|
|
||||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const upper = () => {
|
const upper = () => {
|
||||||
|
@ -205,7 +184,8 @@ export default defineComponent({
|
||||||
focusToot,
|
focusToot,
|
||||||
openSideBar,
|
openSideBar,
|
||||||
heading,
|
heading,
|
||||||
upper
|
upper,
|
||||||
|
account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,10 +5,13 @@
|
||||||
<template v-slot="{ item, index, active }">
|
<template v-slot="{ item, index, active }">
|
||||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||||
<toot
|
<toot
|
||||||
|
v-if="account.account && account.server"
|
||||||
:message="item"
|
:message="item"
|
||||||
:focused="item.uri === focusedId"
|
:focused="item.uri === focusedId"
|
||||||
:overlaid="modalOpened"
|
:overlaid="modalOpened"
|
||||||
:filters="[]"
|
:filters="[]"
|
||||||
|
:account="account.account"
|
||||||
|
:server="account.server"
|
||||||
v-on:update="updateToot"
|
v-on:update="updateToot"
|
||||||
v-on:delete="deleteToot"
|
v-on:delete="deleteToot"
|
||||||
@focusRight="focusSidebar"
|
@focusRight="focusSidebar"
|
||||||
|
@ -27,7 +30,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { logicAnd } from '@vueuse/math'
|
||||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||||
import { useStore } from '@/store'
|
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 CONTENTS_MUTATION } from '@/store/TimelineSpace/Contents'
|
||||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||||
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
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({
|
export default defineComponent({
|
||||||
name: 'favourites',
|
name: 'favourites',
|
||||||
|
@ -47,6 +54,7 @@ export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
const space = 'TimelineSpace/Contents/Favourites'
|
const space = 'TimelineSpace/Contents/Favourites'
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
const route = useRoute()
|
||||||
const i18n = useI18next()
|
const i18n = useI18next()
|
||||||
|
|
||||||
const heading = ref<boolean>(false)
|
const heading = ref<boolean>(false)
|
||||||
|
@ -54,21 +62,32 @@ export default defineComponent({
|
||||||
const scroller = ref<any>()
|
const scroller = ref<any>()
|
||||||
const { j, k, Ctrl_r } = useMagicKeys()
|
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 openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
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 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 modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||||
const currentFocusedIndex = computed(() => favourites.value.findIndex(status => focusedId.value === status.uri))
|
const currentFocusedIndex = computed(() => favourites.value.findIndex(status => focusedId.value === status.uri))
|
||||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
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)
|
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||||
|
|
||||||
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true)
|
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true)
|
||||||
store
|
store
|
||||||
.dispatch(`${space}/${ACTION_TYPES.FETCH_FAVOURITES}`, account.value)
|
.dispatch(`${space}/${ACTION_TYPES.FETCH_FAVOURITES}`, account)
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: i18n.t('message.favourite_fetch_error'),
|
message: i18n.t('message.favourite_fetch_error'),
|
||||||
|
@ -116,12 +135,18 @@ export default defineComponent({
|
||||||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||||
!lazyLoading.value
|
!lazyLoading.value
|
||||||
) {
|
) {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_FAVOURITES}`, favourites.value[favourites.value.length - 1]).catch(() => {
|
lazyLoading.value = true
|
||||||
ElMessage({
|
store
|
||||||
message: i18n.t('message.favourite_fetch_error'),
|
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_FAVOURITES}`, account)
|
||||||
type: 'error'
|
.catch(() => {
|
||||||
|
ElMessage({
|
||||||
|
message: i18n.t('message.favourite_fetch_error'),
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
lazyLoading.value = false
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
// for upper
|
// for upper
|
||||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||||
|
@ -139,7 +164,7 @@ export default defineComponent({
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||||
try {
|
try {
|
||||||
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_FAVOURITES}`, account.value).catch(() => {
|
await store.dispatch(`${space}/${ACTION_TYPES.FETCH_FAVOURITES}`, account).catch(() => {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: i18n.t('message.favourite_fetch_error'),
|
message: i18n.t('message.favourite_fetch_error'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
|
@ -185,7 +210,8 @@ export default defineComponent({
|
||||||
focusToot,
|
focusToot,
|
||||||
openSideBar,
|
openSideBar,
|
||||||
heading,
|
heading,
|
||||||
upper
|
upper,
|
||||||
|
account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,13 +7,17 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, computed, onMounted } from 'vue'
|
import { defineComponent, computed, onMounted, reactive } from 'vue'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { useI18next } from 'vue3-i18next'
|
import { useI18next } from 'vue3-i18next'
|
||||||
import { Entity } from 'megalodon'
|
import { Entity } from 'megalodon'
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
import User from '@/components/molecules/User.vue'
|
import User from '@/components/molecules/User.vue'
|
||||||
import { ACTION_TYPES } from '@/store/TimelineSpace/Contents/FollowRequests'
|
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({
|
export default defineComponent({
|
||||||
name: 'follow-requests',
|
name: 'follow-requests',
|
||||||
|
@ -21,8 +25,17 @@ export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
const space = 'TimelineSpace/Contents/FollowRequests'
|
const space = 'TimelineSpace/Contents/FollowRequests'
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
const route = useRoute()
|
||||||
const i18n = useI18next()
|
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)
|
const requests = computed(() => store.state.TimelineSpace.Contents.FollowRequests.requests)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
@ -30,23 +43,27 @@ export default defineComponent({
|
||||||
})
|
})
|
||||||
|
|
||||||
const initialize = async () => {
|
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({
|
ElMessage({
|
||||||
message: i18n.t('message.timeline_fetch_error'),
|
message: i18n.t('message.timeline_fetch_error'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const accept = (account: Entity.Account) => {
|
const accept = (user: Entity.Account) => {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.ACCEPT_REQUEST}`, account).catch(_ => {
|
store.dispatch(`${space}/${ACTION_TYPES.ACCEPT_REQUEST}`, { user, account: account.account, server: account.server }).catch(_ => {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: i18n.t('message.follow_request_accept_error'),
|
message: i18n.t('message.follow_request_accept_error'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const reject = (account: Entity.Account) => {
|
const reject = (user: Entity.Account) => {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.REJECT_REQUEST}`, account).catch(_ => {
|
store.dispatch(`${space}/${ACTION_TYPES.REJECT_REQUEST}`, { user, account: account.account, server: account.server }).catch(_ => {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: i18n.t('message.follow_request_reject_error'),
|
message: i18n.t('message.follow_request_reject_error'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
|
|
|
@ -11,11 +11,6 @@
|
||||||
<div class="form-item input">
|
<div class="form-item input">
|
||||||
<input v-model="tag" :placeholder="$t('hashtag.tag_name')" class="search-keyword" v-on:keyup.enter="search" autofocus />
|
<input v-model="tag" :placeholder="$t('hashtag.tag_name')" class="search-keyword" v-on:keyup.enter="search" autofocus />
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -26,14 +21,12 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onMounted, ref, watch } from 'vue'
|
import { computed, defineComponent, onMounted, ref, watch } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useStore } from '@/store'
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'hashtag',
|
name: 'hashtag',
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const store = useStore()
|
|
||||||
|
|
||||||
const tag = ref<string>('')
|
const tag = ref<string>('')
|
||||||
const id = computed(() => route.params.id)
|
const id = computed(() => route.params.id)
|
||||||
|
@ -58,16 +51,12 @@ export default defineComponent({
|
||||||
const back = () => {
|
const back = () => {
|
||||||
router.push({ path: `/${id.value}/hashtag` })
|
router.push({ path: `/${id.value}/hashtag` })
|
||||||
}
|
}
|
||||||
const save = () => {
|
|
||||||
store.dispatch('TimelineSpace/Contents/Hashtag/saveTag', tag.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tagPage,
|
tagPage,
|
||||||
tag,
|
tag,
|
||||||
back,
|
back,
|
||||||
search,
|
search
|
||||||
save
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {}
|
methods: {}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div name="tag" class="tag-timeline">
|
<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">
|
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||||
<template v-slot="{ item, index, active }">
|
<template v-slot="{ item, index, active }">
|
||||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||||
<toot
|
<toot
|
||||||
|
v-if="account.account && account.server"
|
||||||
:message="item"
|
:message="item"
|
||||||
:focused="item.uri + item.id === focusedId"
|
:focused="item.uri + item.id === focusedId"
|
||||||
:overlaid="modalOpened"
|
:overlaid="modalOpened"
|
||||||
:filters="[]"
|
:filters="[]"
|
||||||
|
:account="account.account"
|
||||||
|
:server="account.server"
|
||||||
v-on:update="updateToot"
|
v-on:update="updateToot"
|
||||||
v-on:delete="deleteToot"
|
v-on:delete="deleteToot"
|
||||||
@focusRight="focusSidebar"
|
@focusRight="focusSidebar"
|
||||||
|
@ -27,7 +29,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { logicAnd } from '@vueuse/math'
|
||||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||||
import { ElMessage } from 'element-plus'
|
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 HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||||
import { MUTATION_TYPES as CONTENTS_MUTATION } from '@/store/TimelineSpace/Contents'
|
import { MUTATION_TYPES as CONTENTS_MUTATION } from '@/store/TimelineSpace/Contents'
|
||||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Hashtag/Tag'
|
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({
|
export default defineComponent({
|
||||||
name: 'tag',
|
name: 'tag',
|
||||||
|
@ -55,21 +60,31 @@ export default defineComponent({
|
||||||
const { reloadable } = useReloadable(store, route, i18n)
|
const { reloadable } = useReloadable(store, route, i18n)
|
||||||
const { j, k, Ctrl_r } = useMagicKeys()
|
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 { tag } = toRefs(props)
|
||||||
const focusedId = ref<string | null>(null)
|
const focusedId = ref<string | null>(null)
|
||||||
const scroller = ref<any>(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 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 openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
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)
|
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true)
|
||||||
load(tag.value).finally(() => {
|
load(tag.value).finally(() => {
|
||||||
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, false)
|
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, false)
|
||||||
|
@ -92,9 +107,9 @@ export default defineComponent({
|
||||||
})
|
})
|
||||||
watch(focusedId, (newVal, _oldVal) => {
|
watch(focusedId, (newVal, _oldVal) => {
|
||||||
if (newVal && heading.value) {
|
if (newVal && heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
heading.value = false
|
||||||
} else if (newVal === null && !heading.value) {
|
} else if (newVal === null && !heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||||
|
@ -118,13 +133,13 @@ export default defineComponent({
|
||||||
})
|
})
|
||||||
|
|
||||||
const load = async (tag: string) => {
|
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({
|
ElMessage({
|
||||||
message: i18n.t('message.timeline_fetch_error'),
|
message: i18n.t('message.timeline_fetch_error'),
|
||||||
type: '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({
|
ElMessage({
|
||||||
message: i18n.t('message.start_streaming_error'),
|
message: i18n.t('message.start_streaming_error'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
|
@ -133,9 +148,7 @@ export default defineComponent({
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = true
|
||||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_TIMELINE}`)
|
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CLEAR_TIMELINE}`)
|
|
||||||
const el = document.getElementById('scroller')
|
const el = document.getElementById('scroller')
|
||||||
if (el !== undefined && el !== null) {
|
if (el !== undefined && el !== null) {
|
||||||
el.removeEventListener('scroll', onScroll)
|
el.removeEventListener('scroll', onScroll)
|
||||||
|
@ -148,10 +161,13 @@ export default defineComponent({
|
||||||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||||
!lazyLoading.value
|
!lazyLoading.value
|
||||||
) {
|
) {
|
||||||
|
lazyLoading.value = true
|
||||||
store
|
store
|
||||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||||
tag: tag.value,
|
tag: tag.value,
|
||||||
status: timeline.value[timeline.value.length - 1]
|
status: timeline.value[timeline.value.length - 1],
|
||||||
|
account: account.account,
|
||||||
|
server: account.server
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
|
@ -159,13 +175,15 @@ export default defineComponent({
|
||||||
type: 'error'
|
type: 'error'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.finally(() => {
|
||||||
|
lazyLoading.value = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
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) {
|
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = true
|
||||||
store.commit(`${space}/${MUTATION_TYPES.MERGE_UNREADS}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const updateToot = (toot: Entity.Status) => {
|
const updateToot = (toot: Entity.Status) => {
|
||||||
|
@ -232,7 +250,7 @@ export default defineComponent({
|
||||||
openSideBar,
|
openSideBar,
|
||||||
heading,
|
heading,
|
||||||
upper,
|
upper,
|
||||||
unreads
|
account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="home">
|
<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">
|
<DynamicScroller :items="filteredTimeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||||
<template v-slot="{ item, index, active }">
|
<template v-slot="{ item, index, active }">
|
||||||
<template v-if="item.id === 'loading-card'">
|
<template v-if="item.id === 'loading-card'">
|
||||||
|
@ -11,10 +10,13 @@
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||||
<toot
|
<toot
|
||||||
|
v-if="account.account && account.server"
|
||||||
:message="item"
|
:message="item"
|
||||||
:focused="item.uri + item.id === focusedId"
|
:focused="item.uri + item.id === focusedId"
|
||||||
:overlaid="modalOpened"
|
:overlaid="modalOpened"
|
||||||
:filters="filters"
|
:filters="filters"
|
||||||
|
:account="account.account"
|
||||||
|
:server="account.server"
|
||||||
v-on:update="updateToot"
|
v-on:update="updateToot"
|
||||||
v-on:delete="deleteToot"
|
v-on:delete="deleteToot"
|
||||||
@focusRight="focusSidebar"
|
@focusRight="focusSidebar"
|
||||||
|
@ -35,7 +37,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { logicAnd } from '@vueuse/math'
|
||||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
@ -48,9 +50,9 @@ import StatusLoading from '@/components/organisms/StatusLoading.vue'
|
||||||
import { EventEmitter } from '@/components/event'
|
import { EventEmitter } from '@/components/event'
|
||||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Home'
|
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 SIDE_MENU_MUTATION } from '@/store/TimelineSpace/SideMenu'
|
||||||
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
import { LocalAccount } from '~/src/types/localAccount'
|
||||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
import { LocalServer } from '~/src/types/localServer'
|
||||||
import useReloadable from '@/components/utils/reloadable'
|
import { MyWindow } from '~/src/types/global'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'home',
|
name: 'home',
|
||||||
|
@ -60,26 +62,35 @@ export default defineComponent({
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const i18n = useI18next()
|
const i18n = useI18next()
|
||||||
const { reloadable } = useReloadable(store, route, i18n)
|
const { j, k } = useMagicKeys()
|
||||||
const { j, k, Ctrl_r } = useMagicKeys()
|
|
||||||
|
const win = (window as any) as MyWindow
|
||||||
|
|
||||||
|
const id = computed(() => parseInt(route.params.id as string))
|
||||||
|
|
||||||
const focusedId = ref<string | null>(null)
|
const focusedId = ref<string | null>(null)
|
||||||
const loadingMore = ref(false)
|
const loadingMore = ref(false)
|
||||||
const scroller = ref<any>()
|
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 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 modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||||
const filters = computed(() => store.getters[`${space}/filters`])
|
const filters = computed(() => store.getters[`${space}/filters`])
|
||||||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||||
const filteredTimeline = computed(() => {
|
const filteredTimeline = computed(() => {
|
||||||
|
if (!timeline.value) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
return timeline.value.filter(toot => {
|
return timeline.value.filter(toot => {
|
||||||
if ('url' in toot) {
|
if ('url' in toot) {
|
||||||
if (toot.in_reply_to_id) {
|
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)
|
store.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_HOME_TIMELINE}`, false)
|
||||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||||
|
|
||||||
if (heading.value && timeline.value.length > 0) {
|
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
onBeforeUpdate(() => {
|
onBeforeUpdate(() => {
|
||||||
if (store.state.TimelineSpace.SideMenu.unreadHomeTimeline && heading.value) {
|
if (store.state.TimelineSpace.SideMenu.unreadHomeTimeline && heading.value) {
|
||||||
|
@ -109,35 +120,26 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
|
||||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_TIMELINE}`)
|
|
||||||
const el = document.getElementById('scroller')
|
const el = document.getElementById('scroller')
|
||||||
if (el !== undefined && el !== null) {
|
if (el !== undefined && el !== null) {
|
||||||
el.removeEventListener('scroll', onScroll)
|
el.removeEventListener('scroll', onScroll)
|
||||||
el.scrollTop = 0
|
el.scrollTop = 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
watch(startReload, (newVal, oldVal) => {
|
|
||||||
if (!oldVal && newVal) {
|
|
||||||
reload().finally(() => {
|
|
||||||
store.commit(`TimelineSpace/HeaderMenu/${HEADER_MUTATION.CHANGE_RELOAD}`, false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
watch(
|
watch(
|
||||||
timeline,
|
timeline,
|
||||||
(newState, _oldState) => {
|
(newState, _oldState) => {
|
||||||
if (heading.value && newState.length > 0) {
|
if (heading.value && newState.length > 0 && account.account && account.server) {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`)
|
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`, account)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
watch(focusedId, (newVal, _oldVal) => {
|
watch(focusedId, (newVal, _oldVal) => {
|
||||||
if (newVal && heading.value) {
|
if (newVal && heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
heading.value = false
|
||||||
} else if (newVal === null && !heading.value) {
|
} else if (newVal === null && !heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||||
|
@ -150,9 +152,6 @@ export default defineComponent({
|
||||||
whenever(logicAnd(k, shortcutEnabled), () => {
|
whenever(logicAnd(k, shortcutEnabled), () => {
|
||||||
focusPrev()
|
focusPrev()
|
||||||
})
|
})
|
||||||
whenever(logicAnd(Ctrl_r, shortcutEnabled), () => {
|
|
||||||
reload()
|
|
||||||
})
|
|
||||||
|
|
||||||
const onScroll = (event: Event) => {
|
const onScroll = (event: Event) => {
|
||||||
// for lazyLoading
|
// for lazyLoading
|
||||||
|
@ -161,42 +160,49 @@ export default defineComponent({
|
||||||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||||
!lazyLoading.value
|
!lazyLoading.value
|
||||||
) {
|
) {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, timeline.value[timeline.value.length - 1]).catch(() => {
|
lazyLoading.value = true
|
||||||
ElMessage({
|
store
|
||||||
message: i18n.t('message.timeline_fetch_error'),
|
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||||
type: 'error'
|
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) {
|
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) {
|
} else if ((event.target as HTMLElement)!.scrollTop <= 5 && !heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = true
|
||||||
store.commit(`${space}/${MUTATION_TYPES.MERGE_UNREADS}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const updateToot = (message: Entity.Status) => {
|
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) => {
|
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) => {
|
const fetchTimelineSince = (since_id: string) => {
|
||||||
loadingMore.value = true
|
loadingMore.value = true
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.FETCH_TIMELINE_SINCE}`, since_id).finally(() => {
|
store
|
||||||
setTimeout(() => {
|
.dispatch(`${space}/${ACTION_TYPES.FETCH_TIMELINE_SINCE}`, { sinceId: since_id, account: account.account, server: account.server })
|
||||||
loadingMore.value = false
|
.finally(() => {
|
||||||
}, 500)
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const upper = () => {
|
const upper = () => {
|
||||||
scroller.value.scrollToItem(0)
|
scroller.value.scrollToItem(0)
|
||||||
|
@ -240,7 +246,7 @@ export default defineComponent({
|
||||||
openSideBar,
|
openSideBar,
|
||||||
heading,
|
heading,
|
||||||
upper,
|
upper,
|
||||||
unreads
|
account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div name="list" class="list-timeline">
|
<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">
|
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||||
<template v-slot="{ item, index, active }">
|
<template v-slot="{ item, index, active }">
|
||||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||||
<toot
|
<toot
|
||||||
|
v-if="account.account && account.server"
|
||||||
:message="item"
|
:message="item"
|
||||||
:focused="item.uri + item.id === focusedId"
|
:focused="item.uri + item.id === focusedId"
|
||||||
:overlaid="modalOpened"
|
:overlaid="modalOpened"
|
||||||
:filters="[]"
|
:filters="[]"
|
||||||
|
:account="account.account"
|
||||||
|
:server="account.server"
|
||||||
v-on:update="updateToot"
|
v-on:update="updateToot"
|
||||||
v-on:delete="deleteToot"
|
v-on:delete="deleteToot"
|
||||||
@focusRight="focusSidebar"
|
@focusRight="focusSidebar"
|
||||||
|
@ -27,7 +29,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { logicAnd } from '@vueuse/math'
|
||||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||||
import { ElMessage } from 'element-plus'
|
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 { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Lists/Show'
|
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Lists/Show'
|
||||||
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
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({
|
export default defineComponent({
|
||||||
name: 'list',
|
name: 'list',
|
||||||
|
@ -48,24 +54,35 @@ export default defineComponent({
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const space = 'TimelineSpace/Contents/Lists/Show'
|
const space = 'TimelineSpace/Contents/Lists/Show'
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
const route = useRoute()
|
||||||
const i18n = useI18next()
|
const i18n = useI18next()
|
||||||
const { j, k, Ctrl_r } = useMagicKeys()
|
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 { list_id } = toRefs(props)
|
||||||
const focusedId = ref<string | null>(null)
|
const focusedId = ref<string | null>(null)
|
||||||
const scroller = ref<any>(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 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 openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
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)
|
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||||
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true)
|
store.commit(`TimelineSpace/Contents/${CONTENTS_MUTATION.CHANGE_LOADING}`, true)
|
||||||
load().finally(() => {
|
load().finally(() => {
|
||||||
|
@ -87,9 +104,9 @@ export default defineComponent({
|
||||||
})
|
})
|
||||||
watch(focusedId, (newVal, _oldVal) => {
|
watch(focusedId, (newVal, _oldVal) => {
|
||||||
if (newVal && heading.value) {
|
if (newVal && heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
heading.value = false
|
||||||
} else if (newVal === null && !heading.value) {
|
} else if (newVal === null && !heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||||
|
@ -110,9 +127,7 @@ export default defineComponent({
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.STOP_STREAMING}`)
|
store.dispatch(`${space}/${ACTION_TYPES.STOP_STREAMING}`)
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = true
|
||||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_TIMELINE}`)
|
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CLEAR_TIMELINE}`)
|
|
||||||
const el = document.getElementById('scroller')
|
const el = document.getElementById('scroller')
|
||||||
if (el !== undefined && el !== null) {
|
if (el !== undefined && el !== null) {
|
||||||
el.removeEventListener('scroll', onScroll)
|
el.removeEventListener('scroll', onScroll)
|
||||||
|
@ -123,14 +138,18 @@ export default defineComponent({
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
await store.dispatch(`${space}/${ACTION_TYPES.STOP_STREAMING}`)
|
await store.dispatch(`${space}/${ACTION_TYPES.STOP_STREAMING}`)
|
||||||
try {
|
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) {
|
} catch (err) {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: i18n.t('message.timeline_fetch_error'),
|
message: i18n.t('message.timeline_fetch_error'),
|
||||||
type: '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({
|
ElMessage({
|
||||||
message: i18n.t('message.start_streaming_error'),
|
message: i18n.t('message.start_streaming_error'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
|
@ -144,10 +163,13 @@ export default defineComponent({
|
||||||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||||
!lazyLoading
|
!lazyLoading
|
||||||
) {
|
) {
|
||||||
|
lazyLoading.value = true
|
||||||
store
|
store
|
||||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||||
list_id: list_id.value,
|
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(() => {
|
.catch(() => {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
|
@ -155,13 +177,15 @@ export default defineComponent({
|
||||||
type: 'error'
|
type: 'error'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.finally(() => {
|
||||||
|
lazyLoading.value = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
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) {
|
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = true
|
||||||
store.commit(`${space}/${MUTATION_TYPES.MERGE_UNREADS}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
|
@ -227,7 +251,7 @@ export default defineComponent({
|
||||||
openSideBar,
|
openSideBar,
|
||||||
heading,
|
heading,
|
||||||
upper,
|
upper,
|
||||||
unreads
|
account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="local">
|
<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">
|
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||||
<template v-slot="{ item, index, active }">
|
<template v-slot="{ item, index, active }">
|
||||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||||
<toot
|
<toot
|
||||||
|
v-if="account.account && account.server"
|
||||||
:message="item"
|
:message="item"
|
||||||
:focused="item.uri + item.id === focusedId"
|
:focused="item.uri + item.id === focusedId"
|
||||||
:overlaid="modalOpened"
|
:overlaid="modalOpened"
|
||||||
:filters="[]"
|
:filters="[]"
|
||||||
|
:account="account.account"
|
||||||
|
:server="account.server"
|
||||||
v-on:update="updateToot"
|
v-on:update="updateToot"
|
||||||
v-on:delete="deleteToot"
|
v-on:delete="deleteToot"
|
||||||
@focusRight="focusSidebar"
|
@focusRight="focusSidebar"
|
||||||
|
@ -27,7 +29,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { logicAnd } from '@vueuse/math'
|
||||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
@ -37,12 +39,11 @@ import { useRoute } from 'vue-router'
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
import Toot from '@/components/organisms/Toot.vue'
|
import Toot from '@/components/organisms/Toot.vue'
|
||||||
import { EventEmitter } from '@/components/event'
|
import { EventEmitter } from '@/components/event'
|
||||||
import useReloadable from '@/components/utils/reloadable'
|
|
||||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Local'
|
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 SIDE_MENU_MUTATION } from '@/store/TimelineSpace/SideMenu'
|
||||||
import { MUTATION_TYPES as TIMELINE_MUTATION, ACTION_TYPES as TIMELINE_ACTION } from '@/store/TimelineSpace'
|
import { LocalAccount } from '~/src/types/localAccount'
|
||||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
import { LocalServer } from '~/src/types/localServer'
|
||||||
import { MUTATION_TYPES as CONTENTS_MUTATION } from '@/store/TimelineSpace/Contents'
|
import { MyWindow } from '~/src/types/global'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'local',
|
name: 'local',
|
||||||
|
@ -52,32 +53,34 @@ export default defineComponent({
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const i18n = useI18next()
|
const i18n = useI18next()
|
||||||
const { reloadable } = useReloadable(store, route, i18n)
|
const { j, k } = useMagicKeys()
|
||||||
const { j, k, Ctrl_r } = useMagicKeys()
|
|
||||||
|
const win = (window as any) as MyWindow
|
||||||
|
const id = computed(() => parseInt(route.params.id as string))
|
||||||
|
|
||||||
const focusedId = ref<string | null>(null)
|
const focusedId = ref<string | null>(null)
|
||||||
const scroller = ref<any>(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 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 modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||||
|
|
||||||
onMounted(async () => {
|
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)
|
store.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_LOCAL_TIMELINE}`, false)
|
||||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
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(() => {
|
onBeforeUpdate(() => {
|
||||||
if (store.state.TimelineSpace.SideMenu.unreadLocalTimeline && heading.value) {
|
if (store.state.TimelineSpace.SideMenu.unreadLocalTimeline && heading.value) {
|
||||||
|
@ -85,37 +88,21 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
onBeforeUnmount(() => {
|
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')
|
EventEmitter.off('focus-timeline')
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
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')
|
const el = document.getElementById('scroller')
|
||||||
if (el) {
|
if (el) {
|
||||||
el.removeEventListener('scroll', onScroll)
|
el.removeEventListener('scroll', onScroll)
|
||||||
el.scrollTop = 0
|
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) => {
|
watch(focusedId, (newVal, _oldVal) => {
|
||||||
if (newVal && heading.value) {
|
if (newVal && heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
heading.value = false
|
||||||
} else if (newVal === null && !heading.value) {
|
} else if (newVal === null && !heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||||
|
@ -128,53 +115,46 @@ export default defineComponent({
|
||||||
whenever(logicAnd(k, shortcutEnabled), () => {
|
whenever(logicAnd(k, shortcutEnabled), () => {
|
||||||
focusPrev()
|
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) => {
|
const onScroll = (event: Event) => {
|
||||||
if (
|
if (
|
||||||
(event.target as HTMLElement)!.clientHeight + (event.target as HTMLElement)!.scrollTop >=
|
(event.target as HTMLElement)!.clientHeight + (event.target as HTMLElement)!.scrollTop >=
|
||||||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||||
!lazyLoading.value
|
!lazyLoading.value
|
||||||
) {
|
) {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, timeline.value[timeline.value.length - 1]).catch(() => {
|
lazyLoading.value = true
|
||||||
ElMessage({
|
store
|
||||||
message: i18n.t('message.timeline_fetch_error'),
|
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||||
type: 'error'
|
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) {
|
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) {
|
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateToot = (message: Entity.Status) => {
|
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) => {
|
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 = () => {
|
const upper = () => {
|
||||||
scroller.value.scrollToItem(0)
|
scroller.value.scrollToItem(0)
|
||||||
|
@ -213,7 +193,7 @@ export default defineComponent({
|
||||||
openSideBar,
|
openSideBar,
|
||||||
heading,
|
heading,
|
||||||
upper,
|
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>
|
<template>
|
||||||
<div id="notifications">
|
<div id="notifications">
|
||||||
<div></div>
|
<DynamicScroller :items="notifications" :min-item-size="20" id="scroller" class="scroller" ref="scroller">
|
||||||
<DynamicScroller :items="handledNotifications" :min-item-size="20" id="scroller" class="scroller" ref="scroller">
|
|
||||||
<template v-slot="{ item, index, active }">
|
<template v-slot="{ item, index, active }">
|
||||||
<template v-if="item.id === 'loading-card'">
|
<template v-if="item.id === 'loading-card'">
|
||||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.id]" :data-index="index" :watchData="true">
|
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.id]" :data-index="index" :watchData="true">
|
||||||
|
@ -11,10 +10,13 @@
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.url]" :data-index="index" :watchData="true">
|
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.url]" :data-index="index" :watchData="true">
|
||||||
<notification
|
<notification
|
||||||
|
v-if="account.account && account.server"
|
||||||
:message="item"
|
:message="item"
|
||||||
:focused="item.id === focusedId"
|
:focused="item.id === focusedId"
|
||||||
:overlaid="modalOpened"
|
:overlaid="modalOpened"
|
||||||
:filters="filters"
|
:filters="filters"
|
||||||
|
:account="account.account"
|
||||||
|
:server="account.server"
|
||||||
v-on:update="updateToot"
|
v-on:update="updateToot"
|
||||||
@focusRight="focusSidebar"
|
@focusRight="focusSidebar"
|
||||||
@selectNotification="focusNotification(item)"
|
@selectNotification="focusNotification(item)"
|
||||||
|
@ -33,7 +35,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { logicAnd } from '@vueuse/math'
|
||||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
@ -44,11 +46,13 @@ import { EventEmitter } from '@/components/event'
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useI18next } from 'vue3-i18next'
|
import { useI18next } from 'vue3-i18next'
|
||||||
import useReloadable from '@/components/utils/reloadable'
|
|
||||||
import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Notifications'
|
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 SIDE_MENU_MUTATION } from '@/store/TimelineSpace/SideMenu'
|
||||||
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
||||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
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({
|
export default defineComponent({
|
||||||
name: 'notifications',
|
name: 'notifications',
|
||||||
|
@ -59,31 +63,40 @@ export default defineComponent({
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const i18n = useI18next()
|
const i18n = useI18next()
|
||||||
const { reloadable } = useReloadable(store, route, i18n)
|
const { j, k } = useMagicKeys()
|
||||||
const { j, k, Ctrl_r } = useMagicKeys()
|
|
||||||
|
const win = (window as any) as MyWindow
|
||||||
|
|
||||||
|
const id = computed(() => parseInt(route.params.id as string))
|
||||||
|
|
||||||
const focusedId = ref<string | null>(null)
|
const focusedId = ref<string | null>(null)
|
||||||
const loadingMore = ref(false)
|
const loadingMore = ref(false)
|
||||||
const scroller = ref<any>()
|
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 notifications = computed(() => store.state.TimelineSpace.Contents.Notifications.notifications[id.value])
|
||||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.Notifications.lazyLoading)
|
|
||||||
const heading = computed(() => store.state.TimelineSpace.Contents.Notifications.heading)
|
|
||||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
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 modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||||
const filters = computed(() => store.getters[`${space}/filters}`])
|
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 currentFocusedIndex = computed(() => notifications.value.findIndex(notification => focusedId.value === notification.id))
|
||||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
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.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_NOTIFICATIONS}`, false)
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.RESET_BADGE}`)
|
store.dispatch(`${space}/${ACTION_TYPES.RESET_BADGE}`)
|
||||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||||
|
|
||||||
if (heading.value && handledNotifications.value.length > 0) {
|
if (heading.value && notifications.value.length > 0) {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`)
|
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`, account)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
onBeforeUpdate(() => {
|
onBeforeUpdate(() => {
|
||||||
|
@ -92,41 +105,32 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
|
||||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_NOTIFICATIONS}`)
|
|
||||||
const el = document.getElementById('scroller')
|
const el = document.getElementById('scroller')
|
||||||
if (el !== undefined && el !== null) {
|
if (el !== undefined && el !== null) {
|
||||||
el.removeEventListener('scroll', onScroll)
|
el.removeEventListener('scroll', onScroll)
|
||||||
el.scrollTop = 0
|
el.scrollTop = 0
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
watch(startReload, (newVal, oldVal) => {
|
|
||||||
if (!oldVal && newVal) {
|
|
||||||
reload().finally(() => {
|
|
||||||
store.commit(`TimelineSpace/HeaderMenu/${HEADER_MUTATION.CHANGE_RELOAD}`, false)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
watch(
|
watch(
|
||||||
notifications,
|
notifications,
|
||||||
(newState, _oldState) => {
|
(newState, _oldState) => {
|
||||||
if (heading.value && newState.length > 0) {
|
if (heading.value && newState.length > 0 && account.account && account.server) {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`)
|
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`, account)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
watch(focusedId, (newVal, _oldVal) => {
|
watch(focusedId, (newVal, _oldVal) => {
|
||||||
if (newVal && heading.value) {
|
if (newVal && heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
heading.value = false
|
||||||
} else if (newVal === null && !heading.value) {
|
} else if (newVal === null && !heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = true
|
||||||
store.commit(`${space}/${ACTION_TYPES.RESET_BADGE}`)
|
store.commit(`${space}/${ACTION_TYPES.RESET_BADGE}`)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||||
if (focusedId.value === null) {
|
if (focusedId.value === null) {
|
||||||
focusedId.value = handledNotifications.value[0].id
|
focusedId.value = notifications.value[0].id
|
||||||
} else {
|
} else {
|
||||||
focusNext()
|
focusNext()
|
||||||
}
|
}
|
||||||
|
@ -134,9 +138,6 @@ export default defineComponent({
|
||||||
whenever(logicAnd(k, shortcutEnabled), () => {
|
whenever(logicAnd(k, shortcutEnabled), () => {
|
||||||
focusPrev()
|
focusPrev()
|
||||||
})
|
})
|
||||||
whenever(logicAnd(Ctrl_r, shortcutEnabled), () => {
|
|
||||||
reload()
|
|
||||||
})
|
|
||||||
|
|
||||||
const onScroll = (event: Event) => {
|
const onScroll = (event: Event) => {
|
||||||
if (
|
if (
|
||||||
|
@ -144,43 +145,50 @@ export default defineComponent({
|
||||||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||||
!lazyLoading.value
|
!lazyLoading.value
|
||||||
) {
|
) {
|
||||||
|
lazyLoading.value = true
|
||||||
store
|
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(() => {
|
.catch(() => {
|
||||||
ElMessage({
|
ElMessage({
|
||||||
message: i18n.t('message.notification_fetch_error'),
|
message: i18n.t('message.notification_fetch_error'),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.finally(() => {
|
||||||
|
lazyLoading.value = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
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) {
|
} 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.RESET_BADGE}`)
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`)
|
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`, account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const updateToot = (message: Entity.Status) => {
|
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) => {
|
const fetchNotificationsSince = (since_id: string) => {
|
||||||
loadingMore.value = true
|
loadingMore.value = true
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.FETCH_NOTIFICATIONS_SINCE}`, since_id).finally(() => {
|
store
|
||||||
setTimeout(() => {
|
.dispatch(`${space}/${ACTION_TYPES.FETCH_NOTIFICATIONS_SINCE}`, {
|
||||||
loadingMore.value = false
|
sinceId: since_id,
|
||||||
}, 500)
|
account: account.account,
|
||||||
})
|
server: account.server
|
||||||
}
|
})
|
||||||
const reload = async () => {
|
.finally(() => {
|
||||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
setTimeout(() => {
|
||||||
try {
|
loadingMore.value = false
|
||||||
await reloadable()
|
}, 500)
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.RESET_BADGE}`)
|
})
|
||||||
} finally {
|
|
||||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
const upper = () => {
|
const upper = () => {
|
||||||
scroller.value.scrollToItem(0)
|
scroller.value.scrollToItem(0)
|
||||||
|
@ -188,16 +196,16 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
const focusNext = () => {
|
const focusNext = () => {
|
||||||
if (currentFocusedIndex.value === -1) {
|
if (currentFocusedIndex.value === -1) {
|
||||||
focusedId.value = handledNotifications.value[0].id
|
focusedId.value = notifications.value[0].id
|
||||||
} else if (currentFocusedIndex.value < handledNotifications.value.length) {
|
} else if (currentFocusedIndex.value < notifications.value.length) {
|
||||||
focusedId.value = handledNotifications.value[currentFocusedIndex.value + 1].id
|
focusedId.value = notifications.value[currentFocusedIndex.value + 1].id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const focusPrev = () => {
|
const focusPrev = () => {
|
||||||
if (currentFocusedIndex.value === 0) {
|
if (currentFocusedIndex.value === 0) {
|
||||||
focusedId.value = null
|
focusedId.value = null
|
||||||
} else if (currentFocusedIndex.value > 0) {
|
} 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) => {
|
const focusNotification = (notification: Entity.Notification) => {
|
||||||
|
@ -208,7 +216,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handledNotifications,
|
notifications,
|
||||||
loadingMore,
|
loadingMore,
|
||||||
fetchNotificationsSince,
|
fetchNotificationsSince,
|
||||||
focusedId,
|
focusedId,
|
||||||
|
@ -221,7 +229,8 @@ export default defineComponent({
|
||||||
focusNotification,
|
focusNotification,
|
||||||
openSideBar,
|
openSideBar,
|
||||||
heading,
|
heading,
|
||||||
upper
|
upper,
|
||||||
|
account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="public">
|
<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">
|
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||||
<template v-slot="{ item, index, active }">
|
<template v-slot="{ item, index, active }">
|
||||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||||
<toot
|
<toot
|
||||||
|
v-if="account.account && account.server"
|
||||||
:message="item"
|
:message="item"
|
||||||
:focused="item.uri + item.id === focusedId"
|
:focused="item.uri + item.id === focusedId"
|
||||||
:overlaid="modalOpened"
|
:overlaid="modalOpened"
|
||||||
:filters="filters"
|
:filters="filters"
|
||||||
|
:account="account.account"
|
||||||
|
:server="account.server"
|
||||||
v-on:update="updateToot"
|
v-on:update="updateToot"
|
||||||
v-on:delete="deleteToot"
|
v-on:delete="deleteToot"
|
||||||
@focusRight="focusSidebar"
|
@focusRight="focusSidebar"
|
||||||
|
@ -27,7 +29,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<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 { logicAnd } from '@vueuse/math'
|
||||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
@ -37,12 +39,11 @@ import { useRoute } from 'vue-router'
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
import Toot from '@/components/organisms/Toot.vue'
|
import Toot from '@/components/organisms/Toot.vue'
|
||||||
import { EventEmitter } from '@/components/event'
|
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 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 { 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({
|
export default defineComponent({
|
||||||
name: 'public',
|
name: 'public',
|
||||||
|
@ -52,33 +53,34 @@ export default defineComponent({
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const i18n = useI18next()
|
const i18n = useI18next()
|
||||||
const { reloadable } = useReloadable(store, route, i18n)
|
const { j, k } = useMagicKeys()
|
||||||
const { j, k, Ctrl_r } = useMagicKeys()
|
|
||||||
|
const win = (window as any) as MyWindow
|
||||||
|
const id = computed(() => parseInt(route.params.id as string))
|
||||||
|
|
||||||
const focusedId = ref<string | null>(null)
|
const focusedId = ref<string | null>(null)
|
||||||
const scroller = ref<any>(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 timeline = computed(() => store.state.TimelineSpace.Contents.Public.timeline[id.value])
|
||||||
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 openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
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 modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||||
const filters = computed(() => store.getters[`${space}/filters`])
|
const filters = computed(() => store.getters[`${space}/filters`])
|
||||||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||||
|
|
||||||
onMounted(async () => {
|
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)
|
store.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_PUBLIC_TIMELINE}`, false)
|
||||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
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(() => {
|
onBeforeUpdate(() => {
|
||||||
if (store.state.TimelineSpace.SideMenu.unreadPublicTimeline && heading.value) {
|
if (store.state.TimelineSpace.SideMenu.unreadPublicTimeline && heading.value) {
|
||||||
|
@ -86,37 +88,21 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
onBeforeUnmount(() => {
|
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')
|
EventEmitter.off('focus-timeline')
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
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')
|
const el = document.getElementById('scroller')
|
||||||
if (el !== undefined && el !== null) {
|
if (el !== undefined && el !== null) {
|
||||||
el.removeEventListener('scroll', onScroll)
|
el.removeEventListener('scroll', onScroll)
|
||||||
el.scrollTop = 0
|
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) => {
|
watch(focusedId, (newVal, _oldVal) => {
|
||||||
if (newVal && heading.value) {
|
if (newVal && heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
heading.value = false
|
||||||
} else if (newVal === null && !heading.value) {
|
} else if (newVal === null && !heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||||
|
@ -129,54 +115,47 @@ export default defineComponent({
|
||||||
whenever(logicAnd(k, shortcutEnabled), () => {
|
whenever(logicAnd(k, shortcutEnabled), () => {
|
||||||
focusPrev()
|
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) => {
|
const onScroll = (event: Event) => {
|
||||||
if (
|
if (
|
||||||
(event.target as HTMLElement)!.clientHeight + (event.target as HTMLElement)!.scrollTop >=
|
(event.target as HTMLElement)!.clientHeight + (event.target as HTMLElement)!.scrollTop >=
|
||||||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||||
!lazyLoading.value
|
!lazyLoading.value
|
||||||
) {
|
) {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, timeline.value[timeline.value.length - 1]).catch(() => {
|
lazyLoading.value = true
|
||||||
ElMessage({
|
store
|
||||||
message: i18n.t('message.timeline_fetch_error'),
|
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||||
type: 'error'
|
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) {
|
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) {
|
} else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) {
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
heading.value = 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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateToot = (message: Entity.Status) => {
|
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) => {
|
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 = () => {
|
const upper = () => {
|
||||||
scroller.value.scrollToItem(0)
|
scroller.value.scrollToItem(0)
|
||||||
|
@ -216,7 +195,7 @@ export default defineComponent({
|
||||||
openSideBar,
|
openSideBar,
|
||||||
heading,
|
heading,
|
||||||
upper,
|
upper,
|
||||||
unreads
|
account
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -87,9 +87,6 @@ export default defineComponent({
|
||||||
case 'bookmarks':
|
case 'bookmarks':
|
||||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TITLE}`, i18n.t('header_menu.bookmark'))
|
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TITLE}`, i18n.t('header_menu.bookmark'))
|
||||||
break
|
break
|
||||||
case 'mentions':
|
|
||||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TITLE}`, i18n.t('header_menu.mention'))
|
|
||||||
break
|
|
||||||
case 'follow-requests':
|
case 'follow-requests':
|
||||||
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TITLE}`, i18n.t('header_menu.follow_requests'))
|
store.commit(`${space}/${MUTATION_TYPES.UPDATE_TITLE}`, i18n.t('header_menu.follow_requests'))
|
||||||
break
|
break
|
||||||
|
@ -131,16 +128,10 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
const reload = () => {
|
const reload = () => {
|
||||||
switch (route.name) {
|
switch (route.name) {
|
||||||
case 'home':
|
|
||||||
case 'notifications':
|
|
||||||
case 'mentions':
|
|
||||||
case 'favourites':
|
case 'favourites':
|
||||||
case 'bookmarks':
|
case 'bookmarks':
|
||||||
case 'local':
|
|
||||||
case 'public':
|
|
||||||
case 'tag':
|
case 'tag':
|
||||||
case 'list':
|
case 'list':
|
||||||
case 'direct-messages':
|
|
||||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_RELOAD}`, true)
|
store.commit(`${space}/${MUTATION_TYPES.CHANGE_RELOAD}`, true)
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
|
@ -149,16 +140,10 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
const reloadable = () => {
|
const reloadable = () => {
|
||||||
switch (route.name) {
|
switch (route.name) {
|
||||||
case 'home':
|
|
||||||
case 'notifications':
|
|
||||||
case 'mentions':
|
|
||||||
case 'favourites':
|
case 'favourites':
|
||||||
case 'bookmarks':
|
case 'bookmarks':
|
||||||
case 'local':
|
|
||||||
case 'public':
|
|
||||||
case 'tag':
|
case 'tag':
|
||||||
case 'list':
|
case 'list':
|
||||||
case 'direct-messages':
|
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -37,11 +37,7 @@ export default defineComponent({
|
||||||
|
|
||||||
const channelForm = ref<HTMLInputElement>()
|
const channelForm = ref<HTMLInputElement>()
|
||||||
|
|
||||||
const channelList = computed(() =>
|
const channelList = computed(() => store.state.TimelineSpace.Modals.Jump.defaultChannelList)
|
||||||
store.state.TimelineSpace.Modals.Jump.defaultChannelList
|
|
||||||
.concat(store.state.TimelineSpace.Modals.Jump.tagChannelList)
|
|
||||||
.concat(store.state.TimelineSpace.Modals.Jump.listChannelList)
|
|
||||||
)
|
|
||||||
const selectedChannel = computed(() => store.state.TimelineSpace.Modals.Jump.selectedChannel)
|
const selectedChannel = computed(() => store.state.TimelineSpace.Modals.Jump.selectedChannel)
|
||||||
const inputtedChannel = computed({
|
const inputtedChannel = computed({
|
||||||
get: () => store.state.TimelineSpace.Modals.Jump.channel,
|
get: () => store.state.TimelineSpace.Modals.Jump.channel,
|
||||||
|
@ -57,8 +53,6 @@ export default defineComponent({
|
||||||
)
|
)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.SYNC_LIST_CHANNEL}`)
|
|
||||||
store.dispatch(`${space}/${ACTION_TYPES.SYNC_TAG_CHANNEL}`)
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
channelForm.value?.focus()
|
channelForm.value?.focus()
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
<div :class="collapse ? 'profile-narrow' : 'profile-wide'">
|
<div :class="collapse ? 'profile-narrow' : 'profile-wide'">
|
||||||
<div class="account">
|
<div class="account">
|
||||||
<div class="avatar" v-if="collapse">
|
<div class="avatar" v-if="collapse">
|
||||||
<img :src="account.avatar" />
|
<img :src="account.account?.avatar" />
|
||||||
</div>
|
</div>
|
||||||
<div class="acct" v-else>
|
<div class="acct" v-else>
|
||||||
@{{ account.username }}
|
@{{ account.account?.username }}
|
||||||
<span class="domain-name">{{ account.domain }}</span>
|
<span class="domain-name">{{ account.server?.domain }}</span>
|
||||||
</div>
|
</div>
|
||||||
<el-dropdown trigger="click" @command="handleProfile" :title="$t('side_menu.profile')">
|
<el-dropdown trigger="click" @command="handleProfile" :title="$t('side_menu.profile')">
|
||||||
<span class="el-dropdown-link">
|
<span class="el-dropdown-link">
|
||||||
|
@ -71,23 +71,6 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-menu-item>
|
</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
|
<el-menu-item
|
||||||
:index="`/${id}/direct-messages`"
|
:index="`/${id}/direct-messages`"
|
||||||
role="menuitem"
|
role="menuitem"
|
||||||
|
@ -263,13 +246,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, computed, onMounted } from 'vue'
|
import { defineComponent, computed, onMounted, ref, reactive } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useStore } from '@/store'
|
import { useStore } from '@/store'
|
||||||
import { ACTION_TYPES } from '@/store/TimelineSpace/SideMenu'
|
import { ACTION_TYPES } from '@/store/TimelineSpace/SideMenu'
|
||||||
import { ACTION_TYPES as PROFILE_ACTION } from '@/store/TimelineSpace/Contents/SideBar/AccountProfile'
|
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 SIDEBAR_ACTION, MUTATION_TYPES as SIDEBAR_MUTATION } from '@/store/TimelineSpace/Contents/SideBar'
|
||||||
import { ACTION_TYPES as GLOBAL_ACTION } from '@/store/GlobalHeader'
|
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({
|
export default defineComponent({
|
||||||
name: 'side-menu',
|
name: 'side-menu',
|
||||||
|
@ -279,33 +267,102 @@ export default defineComponent({
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
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 unreadHomeTimeline = computed(() => store.state.TimelineSpace.SideMenu.unreadHomeTimeline)
|
||||||
const unreadNotifications = computed(() => store.state.TimelineSpace.SideMenu.unreadNotifications)
|
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 unreadLocalTimeline = computed(() => store.state.TimelineSpace.SideMenu.unreadLocalTimeline)
|
||||||
const unreadDirectMessagesTimeline = computed(() => store.state.TimelineSpace.SideMenu.unreadDirectMessagesTimeline)
|
const unreadDirectMessagesTimeline = computed(() => store.state.TimelineSpace.SideMenu.unreadDirectMessagesTimeline)
|
||||||
const unreadPublicTimeline = computed(() => store.state.TimelineSpace.SideMenu.unreadPublicTimeline)
|
const unreadPublicTimeline = computed(() => store.state.TimelineSpace.SideMenu.unreadPublicTimeline)
|
||||||
const unreadFollowRequests = computed(() => store.state.TimelineSpace.SideMenu.unreadFollowRequests)
|
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 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 themeColor = computed(() => store.state.App.theme.side_menu_color)
|
||||||
const hideGlobalHeader = computed(() => store.state.GlobalHeader.hide)
|
const hideGlobalHeader = computed(() => store.state.GlobalHeader.hide)
|
||||||
|
const userAgent = computed(() => store.state.App.userAgent)
|
||||||
const activeRoute = computed(() => route.path)
|
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.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) => {
|
const handleProfile = (command: string) => {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'show':
|
case 'show':
|
||||||
|
if (!account.account) {
|
||||||
|
return
|
||||||
|
}
|
||||||
store
|
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 => {
|
.then(account => {
|
||||||
store.dispatch(`TimelineSpace/Contents/SideBar/AccountProfile/${PROFILE_ACTION.CHANGE_ACCOUNT}`, account)
|
store.dispatch(`TimelineSpace/Contents/SideBar/AccountProfile/${PROFILE_ACTION.CHANGE_ACCOUNT}`, account)
|
||||||
store.commit(`TimelineSpace/Contents/SideBar/${SIDEBAR_MUTATION.CHANGE_OPEN_SIDEBAR}`, true)
|
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}`)
|
store.dispatch(`TimelineSpace/Contents/SideBar/${SIDEBAR_ACTION.OPEN_ACCOUNT_COMPONENT}`)
|
||||||
break
|
break
|
||||||
case 'edit':
|
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
|
break
|
||||||
case 'settings': {
|
case 'settings': {
|
||||||
const url = `/${id.value}/settings`
|
const url = `/${id.value}/settings`
|
||||||
|
@ -336,7 +395,6 @@ export default defineComponent({
|
||||||
return {
|
return {
|
||||||
unreadHomeTimeline,
|
unreadHomeTimeline,
|
||||||
unreadNotifications,
|
unreadNotifications,
|
||||||
unreadMentions,
|
|
||||||
unreadLocalTimeline,
|
unreadLocalTimeline,
|
||||||
unreadDirectMessagesTimeline,
|
unreadDirectMessagesTimeline,
|
||||||
unreadPublicTimeline,
|
unreadPublicTimeline,
|
||||||
|
|
|
@ -35,6 +35,8 @@
|
||||||
:filters="filters"
|
:filters="filters"
|
||||||
:focused="focused"
|
:focused="focused"
|
||||||
:overlaid="overlaid"
|
:overlaid="overlaid"
|
||||||
|
:account="account"
|
||||||
|
:server="server"
|
||||||
@update="updateToot"
|
@update="updateToot"
|
||||||
@delete="deleteToot"
|
@delete="deleteToot"
|
||||||
@focus-right="$emit('focusRight')"
|
@focus-right="$emit('focusRight')"
|
||||||
|
@ -69,6 +71,8 @@
|
||||||
:filters="filters"
|
:filters="filters"
|
||||||
:focused="focused"
|
:focused="focused"
|
||||||
:overlaid="overlaid"
|
:overlaid="overlaid"
|
||||||
|
:account="account"
|
||||||
|
:server="server"
|
||||||
@focus-right="$emit('focusRight')"
|
@focus-right="$emit('focusRight')"
|
||||||
@select="$emit('selectNotification')"
|
@select="$emit('selectNotification')"
|
||||||
>
|
>
|
||||||
|
@ -117,6 +121,8 @@ import Follow from './Notification/Follow.vue'
|
||||||
import FollowRequest from './Notification/FollowRequest.vue'
|
import FollowRequest from './Notification/FollowRequest.vue'
|
||||||
import Mention from './Notification/Mention.vue'
|
import Mention from './Notification/Mention.vue'
|
||||||
import Status from './Notification/Status.vue'
|
import Status from './Notification/Status.vue'
|
||||||
|
import { LocalAccount } from '~/src/types/localAccount'
|
||||||
|
import { LocalServer } from '~/src/types/localServer'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Notification',
|
name: 'Notification',
|
||||||
|
@ -143,6 +149,14 @@ export default defineComponent({
|
||||||
overlaid: {
|
overlaid: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: () => false
|
default: () => false
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
type: Object as PropType<LocalAccount>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
type: Object as PropType<LocalServer>,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: ['focusRight', 'selectNotification', 'update', 'delete'],
|
emits: ['focusRight', 'selectNotification', 'update', 'delete'],
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
:filters="filters"
|
:filters="filters"
|
||||||
:focused="focused"
|
:focused="focused"
|
||||||
:overlaid="overlaid"
|
:overlaid="overlaid"
|
||||||
|
:account="account"
|
||||||
|
:server="server"
|
||||||
v-on:update="updateToot"
|
v-on:update="updateToot"
|
||||||
v-on:delete="deleteToot"
|
v-on:delete="deleteToot"
|
||||||
@focusRight="$emit('focusRight')"
|
@focusRight="$emit('focusRight')"
|
||||||
|
@ -18,6 +20,8 @@
|
||||||
import { defineComponent, PropType } from 'vue'
|
import { defineComponent, PropType } from 'vue'
|
||||||
import { Entity } from 'megalodon'
|
import { Entity } from 'megalodon'
|
||||||
import Toot from '../Toot.vue'
|
import Toot from '../Toot.vue'
|
||||||
|
import { LocalAccount } from '~/src/types/localAccount'
|
||||||
|
import { LocalServer } from '~/src/types/localServer'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'mention',
|
name: 'mention',
|
||||||
|
@ -37,6 +41,14 @@ export default defineComponent({
|
||||||
overlaid: {
|
overlaid: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
type: Object as PropType<LocalAccount>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
type: Object as PropType<LocalServer>,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { Toot },
|
components: { Toot },
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
:filters="filters"
|
:filters="filters"
|
||||||
:focused="focused"
|
:focused="focused"
|
||||||
:overlaid="overlaid"
|
:overlaid="overlaid"
|
||||||
|
:account="account"
|
||||||
|
:server="server"
|
||||||
v-on:update="updateToot"
|
v-on:update="updateToot"
|
||||||
v-on:delete="deleteToot"
|
v-on:delete="deleteToot"
|
||||||
@focusRight="$emit('focusRight')"
|
@focusRight="$emit('focusRight')"
|
||||||
|
@ -45,6 +47,8 @@ import Toot from '../Toot.vue'
|
||||||
import { usernameWithStyle } from '@/utils/username'
|
import { usernameWithStyle } from '@/utils/username'
|
||||||
import { MUTATION_TYPES as SIDEBAR_MUTATION, ACTION_TYPES as SIDEBAR_ACTION } from '@/store/TimelineSpace/Contents/SideBar'
|
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 { 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({
|
export default defineComponent({
|
||||||
name: 'mention',
|
name: 'mention',
|
||||||
|
@ -64,6 +68,14 @@ export default defineComponent({
|
||||||
overlaid: {
|
overlaid: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
type: Object as PropType<LocalAccount>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
type: Object as PropType<LocalServer>,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: { Toot, FailoverImg },
|
components: { Toot, FailoverImg },
|
||||||
|
|
|
@ -137,7 +137,6 @@
|
||||||
{{ favouritesCount }}
|
{{ favouritesCount }}
|
||||||
</div>
|
</div>
|
||||||
<el-button
|
<el-button
|
||||||
v-if="bookmarkSupported"
|
|
||||||
:class="originalMessage.bookmarked ? 'bookmarked' : 'bookmark'"
|
:class="originalMessage.bookmarked ? 'bookmarked' : 'bookmark'"
|
||||||
link
|
link
|
||||||
:title="$t('cards.toot.bookmark')"
|
:title="$t('cards.toot.bookmark')"
|
||||||
|
@ -149,7 +148,7 @@
|
||||||
<el-button v-if="quoteSupported" link class="quote-btn" @click="openQuote()">
|
<el-button v-if="quoteSupported" link class="quote-btn" @click="openQuote()">
|
||||||
<font-awesome-icon icon="quote-right" size="sm" />
|
<font-awesome-icon icon="quote-right" size="sm" />
|
||||||
</el-button>
|
</el-button>
|
||||||
<template v-if="sns !== 'mastodon'">
|
<template v-if="server!.sns !== 'mastodon'">
|
||||||
<el-popover
|
<el-popover
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
width="281"
|
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 MUTE_ACTION } from '@/store/TimelineSpace/Modals/MuteConfirm'
|
||||||
import { ACTION_TYPES as VIEWER_ACTION } from '@/store/TimelineSpace/Modals/ImageViewer'
|
import { ACTION_TYPES as VIEWER_ACTION } from '@/store/TimelineSpace/Modals/ImageViewer'
|
||||||
import { ACTION_TYPES } from '@/store/organisms/Toot'
|
import { ACTION_TYPES } from '@/store/organisms/Toot'
|
||||||
|
import { LocalAccount } from '~/src/types/localAccount'
|
||||||
|
import { LocalServer } from '~/src/types/localServer'
|
||||||
|
|
||||||
const defaultEmojiIndex = new EmojiIndex(data)
|
const defaultEmojiIndex = new EmojiIndex(data)
|
||||||
|
|
||||||
|
@ -298,16 +299,24 @@ export default defineComponent({
|
||||||
detailed: {
|
detailed: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
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) {
|
setup(props, ctx) {
|
||||||
const space = 'organisms/Toot'
|
const space = 'organisms/Toot'
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const i18n = useI18next()
|
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 { l, h, r, b, f, o, p, i, x } = useMagicKeys()
|
||||||
|
|
||||||
const statusRef = ref<any>(null)
|
const statusRef = ref<any>(null)
|
||||||
|
@ -320,9 +329,6 @@ export default defineComponent({
|
||||||
const displayNameStyle = computed(() => store.state.App.displayNameStyle)
|
const displayNameStyle = computed(() => store.state.App.displayNameStyle)
|
||||||
const timeFormat = computed(() => store.state.App.timeFormat)
|
const timeFormat = computed(() => store.state.App.timeFormat)
|
||||||
const language = computed(() => store.state.App.language)
|
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 shortcutEnabled = computed(() => focused.value && !overlaid.value)
|
||||||
const originalMessage = computed(() => {
|
const originalMessage = computed(() => {
|
||||||
if (message.value.reblog && !message.value.quote) {
|
if (message.value.reblog && !message.value.quote) {
|
||||||
|
@ -352,7 +358,7 @@ export default defineComponent({
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
const isMyMessage = computed(() => {
|
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 application = computed(() => {
|
||||||
const msg = originalMessage.value
|
const msg = originalMessage.value
|
||||||
|
@ -386,7 +392,7 @@ export default defineComponent({
|
||||||
return originalMessage.value.visibility === 'direct'
|
return originalMessage.value.visibility === 'direct'
|
||||||
})
|
})
|
||||||
const quoteSupported = computed(() => {
|
const quoteSupported = computed(() => {
|
||||||
return QuoteSupported(sns.value, account.value.domain)
|
return QuoteSupported(server.value.sns, server.value.domain)
|
||||||
})
|
})
|
||||||
|
|
||||||
whenever(logicAnd(l, shortcutEnabled), () => {
|
whenever(logicAnd(l, shortcutEnabled), () => {
|
||||||
|
@ -677,9 +683,6 @@ export default defineComponent({
|
||||||
displayNameStyle,
|
displayNameStyle,
|
||||||
timeFormat,
|
timeFormat,
|
||||||
language,
|
language,
|
||||||
sns,
|
|
||||||
account,
|
|
||||||
bookmarkSupported,
|
|
||||||
originalMessage,
|
originalMessage,
|
||||||
timestamp,
|
timestamp,
|
||||||
readableTimestamp,
|
readableTimestamp,
|
||||||
|
|
|
@ -4,7 +4,6 @@ import { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||||
import { i18n } from 'i18next'
|
import { i18n } from 'i18next'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { ACTION_TYPES } from '@/store/TimelineSpace'
|
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) {
|
export default function useReloadable(store: Store<RootState>, route: RouteLocationNormalizedLoaded, i18next: i18n) {
|
||||||
async function reloadable() {
|
async function reloadable() {
|
||||||
|
@ -15,11 +14,6 @@ export default function useReloadable(store: Store<RootState>, route: RouteLocat
|
||||||
})
|
})
|
||||||
throw err
|
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
|
return account
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
|
||||||
import Login from '@/components/Login.vue'
|
import Login from '@/components/Login.vue'
|
||||||
import Authorize from '@/components/Authorize.vue'
|
|
||||||
import Preferences from '@/components/Preferences.vue'
|
import Preferences from '@/components/Preferences.vue'
|
||||||
import PreferencesGeneral from '@/components/Preferences/General.vue'
|
import PreferencesGeneral from '@/components/Preferences/General.vue'
|
||||||
import PreferencesAppearance from '@/components/Preferences/Appearance.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 TimelineSpace from '@/components/TimelineSpace.vue'
|
||||||
import TimelineSpaceContentsHome from '@/components/TimelineSpace/Contents/Home.vue'
|
import TimelineSpaceContentsHome from '@/components/TimelineSpace/Contents/Home.vue'
|
||||||
import TimelineSpaceContentsNotifications from '@/components/TimelineSpace/Contents/Notifications.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 TimelineSpaceContentsFavourites from '@/components/TimelineSpace/Contents/Favourites.vue'
|
||||||
import TimelineSpaceContentsLocal from '@/components/TimelineSpace/Contents/Local.vue'
|
import TimelineSpaceContentsLocal from '@/components/TimelineSpace/Contents/Local.vue'
|
||||||
import TimelineSpaceContentsPublic from '@/components/TimelineSpace/Contents/Public.vue'
|
import TimelineSpaceContentsPublic from '@/components/TimelineSpace/Contents/Public.vue'
|
||||||
|
@ -40,12 +38,6 @@ const routes = [
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: Login
|
component: Login
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/authorize',
|
|
||||||
name: 'authorize',
|
|
||||||
component: Authorize,
|
|
||||||
props: route => ({ url: route.query.url, sns: route.query.sns })
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/preferences/',
|
path: '/preferences/',
|
||||||
name: 'preferences',
|
name: 'preferences',
|
||||||
|
@ -130,11 +122,6 @@ const routes = [
|
||||||
name: 'notifications',
|
name: 'notifications',
|
||||||
component: TimelineSpaceContentsNotifications
|
component: TimelineSpaceContentsNotifications
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'mentions',
|
|
||||||
name: 'mentions',
|
|
||||||
component: TimelineSpaceContentsMentions
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: 'follow-requests',
|
path: 'follow-requests',
|
||||||
name: '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 { LocalAccount } from '~/src/types/localAccount'
|
||||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||||
import { RootState } from '@/store'
|
import { RootState } from '@/store'
|
||||||
import { StreamingError } from '~src/errors/streamingError'
|
|
||||||
import { MyWindow } from '~/src/types/global'
|
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 = {
|
export type GlobalHeaderState = {
|
||||||
accounts: Array<LocalAccount>
|
accounts: Array<[LocalAccount, LocalServer]>
|
||||||
changing: boolean
|
changing: boolean
|
||||||
hide: boolean
|
hide: boolean
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ export const MUTATION_TYPES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutations: MutationTree<GlobalHeaderState> = {
|
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
|
state.accounts = accounts
|
||||||
},
|
},
|
||||||
[MUTATION_TYPES.UPDATE_CHANGING]: (state: GlobalHeaderState, value: boolean) => {
|
[MUTATION_TYPES.UPDATE_CHANGING]: (state: GlobalHeaderState, value: boolean) => {
|
||||||
|
@ -41,27 +41,28 @@ export const ACTION_TYPES = {
|
||||||
INIT_LOAD: 'initLoad',
|
INIT_LOAD: 'initLoad',
|
||||||
START_STREAMINGS: 'startStreamings',
|
START_STREAMINGS: 'startStreamings',
|
||||||
LIST_ACCOUNTS: 'listAccounts',
|
LIST_ACCOUNTS: 'listAccounts',
|
||||||
REFRESH_ACCOUNTS: 'refreshAccounts',
|
|
||||||
WATCH_SHORTCUT_EVENTS: 'watchShortcutEvents',
|
WATCH_SHORTCUT_EVENTS: 'watchShortcutEvents',
|
||||||
REMOVE_SHORTCUT_EVENTS: 'removeShortcutEvents',
|
REMOVE_SHORTCUT_EVENTS: 'removeShortcutEvents',
|
||||||
LOAD_HIDE: 'loadHide',
|
LOAD_HIDE: 'loadHide',
|
||||||
SWITCH_HIDE: 'switchHide',
|
SWITCH_HIDE: 'switchHide',
|
||||||
START_USER_STREAMINGS: 'startUserStreamings',
|
LOAD_TIMELINES: 'loadTimelines',
|
||||||
STOP_USER_STREAMINGS: 'stopUserStreamings',
|
BIND_STREAMINGS: 'bindStreamings',
|
||||||
BIND_NOTIFICATION: 'bindNotification'
|
BIND_NOTIFICATION: 'bindNotification'
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions: ActionTree<GlobalHeaderState, RootState> = {
|
const actions: ActionTree<GlobalHeaderState, RootState> = {
|
||||||
initLoad: async ({ dispatch }): Promise<Array<LocalAccount>> => {
|
[ACTION_TYPES.INIT_LOAD]: async ({ dispatch }): Promise<Array<LocalAccount>> => {
|
||||||
// Ignore error
|
// Ignore error
|
||||||
try {
|
try {
|
||||||
await dispatch('removeShortcutEvents')
|
await dispatch(ACTION_TYPES.REMOVE_SHORTCUT_EVENTS)
|
||||||
await dispatch('loadHide')
|
await dispatch(ACTION_TYPES.LOAD_HIDE)
|
||||||
dispatch('watchShortcutEvents')
|
dispatch(ACTION_TYPES.WATCH_SHORTCUT_EVENTS)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(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.
|
// Block to root path when user use browser-back, like mouse button.
|
||||||
// Because any contents are not rendered when browser back to / from home.
|
// Because any contents are not rendered when browser back to / from home.
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
|
@ -71,28 +72,17 @@ const actions: ActionTree<GlobalHeaderState, RootState> = {
|
||||||
})
|
})
|
||||||
return accounts
|
return accounts
|
||||||
},
|
},
|
||||||
startStreamings: async ({ dispatch }) => {
|
[ACTION_TYPES.LIST_ACCOUNTS]: async ({ commit }): Promise<Array<[LocalAccount, LocalServer]>> => {
|
||||||
dispatch('bindNotification')
|
const accounts: Array<[LocalAccount, LocalServer]> = await win.ipcRenderer.invoke('list-accounts')
|
||||||
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')
|
|
||||||
commit(MUTATION_TYPES.UPDATE_ACCOUNTS, accounts)
|
commit(MUTATION_TYPES.UPDATE_ACCOUNTS, accounts)
|
||||||
return accounts
|
return accounts
|
||||||
},
|
},
|
||||||
watchShortcutEvents: ({ state, commit, rootState, rootGetters }) => {
|
[ACTION_TYPES.WATCH_SHORTCUT_EVENTS]: ({ state, commit, rootState, rootGetters }) => {
|
||||||
win.ipcRenderer.on('change-account', (_, account: LocalAccount) => {
|
win.ipcRenderer.on('change-account', (_, account: LocalAccount) => {
|
||||||
if (state.changing) {
|
if (state.changing) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if ((rootState.route.params.id as string) === account._id!) {
|
if ((rootState.route.params.id as string) === account[0].id) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// When the modal window is active, don't change account
|
// When the modal window is active, don't change account
|
||||||
|
@ -101,46 +91,81 @@ const actions: ActionTree<GlobalHeaderState, RootState> = {
|
||||||
}
|
}
|
||||||
// changing finish after loading
|
// changing finish after loading
|
||||||
commit(MUTATION_TYPES.UPDATE_CHANGING, true)
|
commit(MUTATION_TYPES.UPDATE_CHANGING, true)
|
||||||
router.push(`/${account._id}/home`)
|
router.push(`/${account[0].id}/home`)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
removeShortcutEvents: async () => {
|
[ACTION_TYPES.REMOVE_SHORTCUT_EVENTS]: async () => {
|
||||||
win.ipcRenderer.removeAllListeners('change-account')
|
win.ipcRenderer.removeAllListeners('change-account')
|
||||||
return true
|
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')
|
const hide: boolean = await win.ipcRenderer.invoke('get-global-header')
|
||||||
commit(MUTATION_TYPES.CHANGE_HIDE, hide)
|
commit(MUTATION_TYPES.CHANGE_HIDE, hide)
|
||||||
return 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)
|
await win.ipcRenderer.invoke('change-global-header', hide)
|
||||||
dispatch('loadHide')
|
dispatch(ACTION_TYPES.LOAD_HIDE)
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
startUserStreamings: ({ state }): Promise<{}> => {
|
[ACTION_TYPES.BIND_NOTIFICATION]: () => {
|
||||||
// @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: () => {
|
|
||||||
win.ipcRenderer.removeAllListeners('open-notification-tab')
|
win.ipcRenderer.removeAllListeners('open-notification-tab')
|
||||||
win.ipcRenderer.on('open-notification-tab', (_, id: string) => {
|
win.ipcRenderer.on('open-notification-tab', (_, id: string) => {
|
||||||
router.push(`/${id}/home`)
|
router.push(`/${id}/home`)
|
||||||
// We have to wait until change el-menu-item
|
// We have to wait until change el-menu-item
|
||||||
setTimeout(() => router.push(`/${id}/notifications`), 500)
|
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 { Module, MutationTree, ActionTree } from 'vuex'
|
||||||
|
import { detector } from 'megalodon'
|
||||||
import { RootState } from '@/store'
|
import { RootState } from '@/store'
|
||||||
import { MyWindow } from '~/src/types/global'
|
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 = {
|
export type LoginState = {
|
||||||
selectedInstance: string | null
|
domain: string | null
|
||||||
searching: boolean
|
searching: boolean
|
||||||
|
server: LocalServer | null
|
||||||
|
appData: OAuth.AppData | null
|
||||||
sns: 'mastodon' | 'pleroma' | 'misskey'
|
sns: 'mastodon' | 'pleroma' | 'misskey'
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = (): LoginState => ({
|
const state = (): LoginState => ({
|
||||||
selectedInstance: null,
|
domain: null,
|
||||||
searching: false,
|
searching: false,
|
||||||
|
server: null,
|
||||||
|
appData: null,
|
||||||
sns: 'mastodon'
|
sns: 'mastodon'
|
||||||
})
|
})
|
||||||
|
|
||||||
export const MUTATION_TYPES = {
|
export const MUTATION_TYPES = {
|
||||||
CHANGE_INSTANCE: 'changeInstance',
|
CHANGE_DOMAIN: 'changeDomain',
|
||||||
CHANGE_SEARCHING: 'changeSearching',
|
CHANGE_SEARCHING: 'changeSearching',
|
||||||
|
CHANGE_SERVER: 'changeServer',
|
||||||
|
CHANGE_APP_DATA: 'changeAppData',
|
||||||
CHANGE_SNS: 'changeSNS'
|
CHANGE_SNS: 'changeSNS'
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutations: MutationTree<LoginState> = {
|
const mutations: MutationTree<LoginState> = {
|
||||||
[MUTATION_TYPES.CHANGE_INSTANCE]: (state: LoginState, instance: string) => {
|
[MUTATION_TYPES.CHANGE_DOMAIN]: (state: LoginState, instance: string | null) => {
|
||||||
state.selectedInstance = instance
|
state.domain = instance
|
||||||
},
|
},
|
||||||
[MUTATION_TYPES.CHANGE_SEARCHING]: (state: LoginState, searching: boolean) => {
|
[MUTATION_TYPES.CHANGE_SEARCHING]: (state: LoginState, searching: boolean) => {
|
||||||
state.searching = searching
|
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') => {
|
[MUTATION_TYPES.CHANGE_SNS]: (state: LoginState, sns: 'mastodon' | 'pleroma' | 'misskey') => {
|
||||||
state.sns = sns
|
state.sns = sns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ACTION_TYPES = {
|
export const ACTION_TYPES = {
|
||||||
FETCH_LOGIN: 'fetchLogin',
|
ADD_SERVER: 'addServer',
|
||||||
|
ADD_APP: 'addApp',
|
||||||
|
AUTHORIZE: 'authorize',
|
||||||
PAGE_BACK: 'pageBack',
|
PAGE_BACK: 'pageBack',
|
||||||
CONFIRM_INSTANCE: 'confirmInstance'
|
CONFIRM_INSTANCE: 'confirmInstance'
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions: ActionTree<LoginState, RootState> = {
|
const actions: ActionTree<LoginState, RootState> = {
|
||||||
[ACTION_TYPES.FETCH_LOGIN]: async ({ state }): Promise<string> => {
|
[ACTION_TYPES.ADD_SERVER]: async ({ state, commit }): Promise<LocalServer> => {
|
||||||
const url = await win.ipcRenderer.invoke('get-auth-url', {
|
const server = await win.ipcRenderer.invoke('add-server', state.domain)
|
||||||
instance: state.selectedInstance,
|
commit(MUTATION_TYPES.CHANGE_SERVER, server)
|
||||||
sns: state.sns
|
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 }) => {
|
[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> => {
|
[ACTION_TYPES.CONFIRM_INSTANCE]: async ({ commit }, domain: string): Promise<boolean> => {
|
||||||
commit(MUTATION_TYPES.CHANGE_SEARCHING, true)
|
commit(MUTATION_TYPES.CHANGE_SEARCHING, true)
|
||||||
const cleanDomain = domain.trim()
|
const cleanDomain = domain.trim()
|
||||||
try {
|
try {
|
||||||
const sns = await detector(`https://${cleanDomain}`)
|
const sns = await detector(`https://${cleanDomain}`)
|
||||||
commit(MUTATION_TYPES.CHANGE_INSTANCE, cleanDomain)
|
commit(MUTATION_TYPES.CHANGE_DOMAIN, cleanDomain)
|
||||||
commit(MUTATION_TYPES.CHANGE_SNS, sns)
|
commit(MUTATION_TYPES.CHANGE_SNS, sns)
|
||||||
} finally {
|
} finally {
|
||||||
commit(MUTATION_TYPES.CHANGE_SEARCHING, false)
|
commit(MUTATION_TYPES.CHANGE_SEARCHING, false)
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||||
import { toRaw } from 'vue'
|
|
||||||
import { LocalAccount } from '~/src/types/localAccount'
|
import { LocalAccount } from '~/src/types/localAccount'
|
||||||
import { RootState } from '@/store'
|
import { RootState } from '@/store'
|
||||||
import { MyWindow } from '~/src/types/global'
|
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 = {
|
export type AccountState = {
|
||||||
accounts: Array<LocalAccount>
|
accounts: Array<[LocalAccount, LocalServer]>
|
||||||
accountLoading: boolean
|
accountLoading: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ export const MUTATION_TYPES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutations: MutationTree<AccountState> = {
|
const mutations: MutationTree<AccountState> = {
|
||||||
[MUTATION_TYPES.UPDATE_ACCOUNTS]: (state, accounts: Array<LocalAccount>) => {
|
[MUTATION_TYPES.UPDATE_ACCOUNTS]: (state, accounts: Array<[LocalAccount, LocalServer]>) => {
|
||||||
state.accounts = accounts
|
state.accounts = accounts
|
||||||
},
|
},
|
||||||
[MUTATION_TYPES.UPDATE_ACCOUNT_LOADING]: (state, value: boolean) => {
|
[MUTATION_TYPES.UPDATE_ACCOUNT_LOADING]: (state, value: boolean) => {
|
||||||
|
@ -39,19 +39,19 @@ export const ACTION_TYPES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const actions: ActionTree<AccountState, RootState> = {
|
const actions: ActionTree<AccountState, RootState> = {
|
||||||
[ACTION_TYPES.LOAD_ACCOUNTS]: async ({ commit }): Promise<Array<LocalAccount>> => {
|
[ACTION_TYPES.LOAD_ACCOUNTS]: async ({ commit }): Promise<Array<[LocalAccount, LocalServer]>> => {
|
||||||
const accounts = await win.ipcRenderer.invoke('list-accounts')
|
const accounts: Array<[LocalAccount, LocalServer]> = await win.ipcRenderer.invoke('list-accounts')
|
||||||
commit(MUTATION_TYPES.UPDATE_ACCOUNTS, accounts)
|
commit(MUTATION_TYPES.UPDATE_ACCOUNTS, accounts)
|
||||||
return accounts
|
return accounts
|
||||||
},
|
},
|
||||||
[ACTION_TYPES.REMOVE_ACCOUNT]: async (_, account: LocalAccount) => {
|
[ACTION_TYPES.REMOVE_ACCOUNT]: async (_, id: number) => {
|
||||||
await win.ipcRenderer.invoke('remove-account', account._id)
|
await win.ipcRenderer.invoke('remove-account', id)
|
||||||
},
|
},
|
||||||
[ACTION_TYPES.FORWARD_ACCOUNT]: async (_, account: LocalAccount) => {
|
[ACTION_TYPES.FORWARD_ACCOUNT]: async (_, id: number) => {
|
||||||
await win.ipcRenderer.invoke('forward-account', toRaw(account))
|
await win.ipcRenderer.invoke('forward-account', id)
|
||||||
},
|
},
|
||||||
[ACTION_TYPES.BACKWARD_ACCOUNT]: async (_, account: LocalAccount) => {
|
[ACTION_TYPES.BACKWARD_ACCOUNT]: async (_, id: number) => {
|
||||||
await win.ipcRenderer.invoke('backward-account', toRaw(account))
|
await win.ipcRenderer.invoke('backward-account', id)
|
||||||
},
|
},
|
||||||
[ACTION_TYPES.REMOVE_ALL_ACCOUNTS]: async () => {
|
[ACTION_TYPES.REMOVE_ALL_ACCOUNTS]: async () => {
|
||||||
await win.ipcRenderer.invoke('remove-all-accounts')
|
await win.ipcRenderer.invoke('remove-all-accounts')
|
||||||
|
|
|
@ -5,20 +5,20 @@ import { Module, MutationTree } from 'vuex'
|
||||||
import { RootState } from '@/store'
|
import { RootState } from '@/store'
|
||||||
|
|
||||||
export type SettingsState = {
|
export type SettingsState = {
|
||||||
accountID: string | null
|
accountId: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = (): SettingsState => ({
|
const state = (): SettingsState => ({
|
||||||
accountID: null
|
accountId: null
|
||||||
})
|
})
|
||||||
|
|
||||||
export const MUTATION_TYPES = {
|
export const MUTATION_TYPES = {
|
||||||
CHANGE_ACCOUNT_ID: 'changeAccountID'
|
CHANGE_ACCOUNT_ID: 'changeAccountId'
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutations: MutationTree<SettingsState> = {
|
const mutations: MutationTree<SettingsState> = {
|
||||||
[MUTATION_TYPES.CHANGE_ACCOUNT_ID]: (state, id: string) => {
|
[MUTATION_TYPES.CHANGE_ACCOUNT_ID]: (state, id: number) => {
|
||||||
state.accountID = id
|
state.accountId = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,9 @@ export const ACTION_TYPES = {
|
||||||
export const actions: ActionTree<FiltersState, RootState> = {
|
export const actions: ActionTree<FiltersState, RootState> = {
|
||||||
[ACTION_TYPES.FETCH_FILTERS]: async ({ commit, rootState }): Promise<Array<Entity.Filter>> => {
|
[ACTION_TYPES.FETCH_FILTERS]: async ({ commit, rootState }): Promise<Array<Entity.Filter>> => {
|
||||||
const client = generator(
|
const client = generator(
|
||||||
rootState.TimelineSpace.sns,
|
rootState.TimelineSpace.server!.sns,
|
||||||
rootState.TimelineSpace.account.baseURL,
|
rootState.TimelineSpace.server!.baseURL,
|
||||||
rootState.TimelineSpace.account.accessToken,
|
rootState.TimelineSpace.account!.accessToken,
|
||||||
rootState.App.userAgent
|
rootState.App.userAgent
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
|
@ -52,9 +52,9 @@ export const actions: ActionTree<FiltersState, RootState> = {
|
||||||
},
|
},
|
||||||
[ACTION_TYPES.DELETE_FILTER]: async ({ commit, dispatch, rootState }, id: string) => {
|
[ACTION_TYPES.DELETE_FILTER]: async ({ commit, dispatch, rootState }, id: string) => {
|
||||||
const client = generator(
|
const client = generator(
|
||||||
rootState.TimelineSpace.sns,
|
rootState.TimelineSpace.server!.sns,
|
||||||
rootState.TimelineSpace.account.baseURL,
|
rootState.TimelineSpace.server!.baseURL,
|
||||||
rootState.TimelineSpace.account.accessToken,
|
rootState.TimelineSpace.account!.accessToken,
|
||||||
rootState.App.userAgent
|
rootState.App.userAgent
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -42,9 +42,9 @@ export const ACTION_TYPES = {
|
||||||
export const actions: ActionTree<EditFiltersState, RootState> = {
|
export const actions: ActionTree<EditFiltersState, RootState> = {
|
||||||
fetchFilter: async ({ commit, rootState }, id: string): Promise<Entity.Filter> => {
|
fetchFilter: async ({ commit, rootState }, id: string): Promise<Entity.Filter> => {
|
||||||
const client = generator(
|
const client = generator(
|
||||||
rootState.TimelineSpace.sns,
|
rootState.TimelineSpace.server!.sns,
|
||||||
rootState.TimelineSpace.account.baseURL,
|
rootState.TimelineSpace.server!.baseURL,
|
||||||
rootState.TimelineSpace.account.accessToken,
|
rootState.TimelineSpace.account!.accessToken,
|
||||||
rootState.App.userAgent
|
rootState.App.userAgent
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
|
@ -65,9 +65,9 @@ export const actions: ActionTree<EditFiltersState, RootState> = {
|
||||||
throw new Error('filter is not set')
|
throw new Error('filter is not set')
|
||||||
}
|
}
|
||||||
const client = generator(
|
const client = generator(
|
||||||
rootState.TimelineSpace.sns,
|
rootState.TimelineSpace.server!.sns,
|
||||||
rootState.TimelineSpace.account.baseURL,
|
rootState.TimelineSpace.server!.baseURL,
|
||||||
rootState.TimelineSpace.account.accessToken,
|
rootState.TimelineSpace.account!.accessToken,
|
||||||
rootState.App.userAgent
|
rootState.App.userAgent
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -54,9 +54,9 @@ export const actions: ActionTree<NewFiltersState, RootState> = {
|
||||||
throw new Error('filter is not set')
|
throw new Error('filter is not set')
|
||||||
}
|
}
|
||||||
const client = generator(
|
const client = generator(
|
||||||
rootState.TimelineSpace.sns,
|
rootState.TimelineSpace.server!.sns,
|
||||||
rootState.TimelineSpace.account.baseURL,
|
rootState.TimelineSpace.server!.baseURL,
|
||||||
rootState.TimelineSpace.account.accessToken,
|
rootState.TimelineSpace.account!.accessToken,
|
||||||
rootState.App.userAgent
|
rootState.App.userAgent
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -36,9 +36,9 @@ export const ACTION_TYPES = {
|
||||||
const actions: ActionTree<GeneralState, RootState> = {
|
const actions: ActionTree<GeneralState, RootState> = {
|
||||||
[ACTION_TYPES.FETCH_SETTINGS]: async ({ commit, rootState }): Promise<Entity.Account> => {
|
[ACTION_TYPES.FETCH_SETTINGS]: async ({ commit, rootState }): Promise<Entity.Account> => {
|
||||||
const client = generator(
|
const client = generator(
|
||||||
rootState.TimelineSpace.sns,
|
rootState.TimelineSpace.server!.sns,
|
||||||
rootState.TimelineSpace.account.baseURL,
|
rootState.TimelineSpace.server!.baseURL,
|
||||||
rootState.TimelineSpace.account.accessToken,
|
rootState.TimelineSpace.account!.accessToken,
|
||||||
rootState.App.userAgent
|
rootState.App.userAgent
|
||||||
)
|
)
|
||||||
const res = await client.verifyAccountCredentials()
|
const res = await client.verifyAccountCredentials()
|
||||||
|
@ -51,9 +51,9 @@ const actions: ActionTree<GeneralState, RootState> = {
|
||||||
},
|
},
|
||||||
[ACTION_TYPES.SET_VISIBILITY]: async ({ commit, rootState }, value: number) => {
|
[ACTION_TYPES.SET_VISIBILITY]: async ({ commit, rootState }, value: number) => {
|
||||||
const client = generator(
|
const client = generator(
|
||||||
rootState.TimelineSpace.sns,
|
rootState.TimelineSpace.server!.sns,
|
||||||
rootState.TimelineSpace.account.baseURL,
|
rootState.TimelineSpace.server!.baseURL,
|
||||||
rootState.TimelineSpace.account.accessToken,
|
rootState.TimelineSpace.account!.accessToken,
|
||||||
rootState.App.userAgent
|
rootState.App.userAgent
|
||||||
)
|
)
|
||||||
const visibility: VisibilityType | undefined = (Object.values(Visibility) as Array<VisibilityType>).find(v => {
|
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) => {
|
[ACTION_TYPES.SET_SENSITIVE]: async ({ commit, rootState }, value: boolean) => {
|
||||||
const client = generator(
|
const client = generator(
|
||||||
rootState.TimelineSpace.sns,
|
rootState.TimelineSpace.server!.sns,
|
||||||
rootState.TimelineSpace.account.baseURL,
|
rootState.TimelineSpace.server!.baseURL,
|
||||||
rootState.TimelineSpace.account.accessToken,
|
rootState.TimelineSpace.account!.accessToken,
|
||||||
rootState.App.userAgent
|
rootState.App.userAgent
|
||||||
)
|
)
|
||||||
const res = await client.updateCredentials({ source: { sensitive: value } })
|
const res = await client.updateCredentials({ source: { sensitive: value } })
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
import { Module, MutationTree, ActionTree } from 'vuex'
|
import { Module, MutationTree, ActionTree } from 'vuex'
|
||||||
import { toRaw } from 'vue'
|
|
||||||
import { RootState } from '@/store'
|
import { RootState } from '@/store'
|
||||||
import { MyWindow } from '~/src/types/global'
|
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'
|
import { DefaultSetting } from '~/src/constants/initializer/setting'
|
||||||
|
|
||||||
const win = window as any as MyWindow
|
const win = (window as any) as MyWindow
|
||||||
|
|
||||||
export type TimelineState = {
|
export type TimelineState = {
|
||||||
setting: TimelineSetting
|
setting: Setting
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = (): TimelineState => ({
|
const state = (): TimelineState => ({
|
||||||
setting: DefaultSetting.timeline
|
setting: DefaultSetting
|
||||||
})
|
})
|
||||||
|
|
||||||
export const MUTATION_TYPES = {
|
export const MUTATION_TYPES = {
|
||||||
|
@ -20,7 +19,7 @@ export const MUTATION_TYPES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mutations: MutationTree<TimelineState> = {
|
const mutations: MutationTree<TimelineState> = {
|
||||||
[MUTATION_TYPES.UPDATE_TIMELINE_SETTING]: (state, setting: TimelineSetting) => {
|
[MUTATION_TYPES.UPDATE_TIMELINE_SETTING]: (state, setting: Setting) => {
|
||||||
state.setting = setting
|
state.setting = setting
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,34 +32,16 @@ export const ACTION_TYPES = {
|
||||||
|
|
||||||
const actions: ActionTree<TimelineState, RootState> = {
|
const actions: ActionTree<TimelineState, RootState> = {
|
||||||
[ACTION_TYPES.LOAD_TIMELINE_SETTING]: async ({ commit, rootState }): Promise<boolean> => {
|
[ACTION_TYPES.LOAD_TIMELINE_SETTING]: async ({ commit, rootState }): Promise<boolean> => {
|
||||||
const setting: Setting = await win.ipcRenderer.invoke('get-account-setting', rootState.Settings.accountID)
|
const setting: Setting = await win.ipcRenderer.invoke('get-account-setting', rootState.Settings.accountId)
|
||||||
commit(MUTATION_TYPES.UPDATE_TIMELINE_SETTING, setting.timeline)
|
commit(MUTATION_TYPES.UPDATE_TIMELINE_SETTING, setting)
|
||||||
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')
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
[ACTION_TYPES.CHANGE_USER_MARKER]: async ({ dispatch, state, rootState }, timeline: { key: boolean }) => {
|
[ACTION_TYPES.CHANGE_USER_MARKER]: async ({ dispatch, state, rootState }, timeline: { key: boolean }) => {
|
||||||
const marker: UseMarker = Object.assign({}, state.setting.useMarker, timeline)
|
const setting: Setting = Object.assign({}, state.setting, timeline)
|
||||||
const tl: TimelineSetting = Object.assign({}, toRaw(state.setting), {
|
setting.accountId = rootState.Settings.accountId!
|
||||||
useMarker: marker
|
console.log(setting)
|
||||||
})
|
|
||||||
const setting: Setting = {
|
|
||||||
accountID: rootState.Settings.accountID!,
|
|
||||||
timeline: tl
|
|
||||||
}
|
|
||||||
await win.ipcRenderer.invoke('update-account-setting', setting)
|
await win.ipcRenderer.invoke('update-account-setting', setting)
|
||||||
dispatch('loadTimelineSetting')
|
dispatch(ACTION_TYPES.LOAD_TIMELINE_SETTING)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue