1
0
mirror of https://github.com/h3poteto/whalebird-desktop synced 2025-01-31 01:27:26 +01:00

Merge pull request #873 from h3poteto/iss-850

refs #850 Replace Modals with typescript
This commit is contained in:
AkiraFukushima 2019-04-11 22:41:51 +09:00 committed by GitHub
commit 1d168268b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1103 additions and 792 deletions

12
package-lock.json generated
View File

@ -2003,6 +2003,12 @@
"integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==",
"dev": true
},
"@types/lodash": {
"version": "4.14.123",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.123.tgz",
"integrity": "sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q==",
"dev": true
},
"@types/node": {
"version": "11.11.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.4.tgz",
@ -13466,9 +13472,9 @@
"dev": true
},
"megalodon": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/megalodon/-/megalodon-0.6.2.tgz",
"integrity": "sha512-EmNs0M6e2AiX9hutoiXo0FUkghZ1HdyLpS8mVkrMMN8btBR2x1hVrAF/8WAFePeJQrEMYjRyQSEJfykJ/4rwaQ==",
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/megalodon/-/megalodon-0.6.3.tgz",
"integrity": "sha512-rxh9Kbbwm9Hnn/e8xdzH2Fw5kD/TamgyGFEzOcsnKCqF4iI2qHuojCBm7KeWohgRlwJ9oq7QYVReEVTipqI8kQ==",
"requires": {
"@types/oauth": "0.9.1",
"@types/request": "2.48.1",

View File

@ -145,7 +145,7 @@
"i18next-sync-fs-backend": "^1.1.0",
"is-empty": "^1.2.0",
"lodash": "^4.17.11",
"megalodon": "0.6.2",
"megalodon": "0.6.3",
"moment": "^2.21.0",
"mousetrap": "^1.6.2",
"nedb": "^1.8.0",
@ -173,6 +173,7 @@
"@mapbox/stylelint-processor-arbitrary-tags": "^0.2.0",
"@types/i18next": "^12.1.0",
"@types/jest": "^24.0.11",
"@types/lodash": "^4.14.123",
"@types/node": "^11.11.4",
"@typescript-eslint/eslint-plugin": "^1.5.0",
"@typescript-eslint/parser": "^1.5.0",

View File

@ -2,7 +2,7 @@ import { Response, Account } from 'megalodon'
import mockedMegalodon from '~/spec/mock/megalodon'
import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import AddListMember from '~/src/renderer/store/TimelineSpace/Modals/AddListMember'
import AddListMember, { AddListMemberState } from '@/store/TimelineSpace/Modals/AddListMember'
jest.mock('megalodon')
@ -28,7 +28,7 @@ const account: Account = {
bot: false
}
const state = () => {
const state = (): AddListMemberState => {
return {
modalOpen: false,
accounts: [],
@ -121,7 +121,7 @@ describe('AddListMember', () => {
mockedMegalodon.mockImplementation(() => mockClient)
const result = await store.dispatch('AddListMember/add', 'akira')
expect(result.data).toEqual({})
expect(result).toEqual({})
})
})
})

View File

@ -1,8 +1,8 @@
import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import ImageViewer from '~/src/renderer/store/TimelineSpace/Modals/ImageViewer'
import ImageViewer, { ImageViewerState } from '~/src/renderer/store/TimelineSpace/Modals/ImageViewer'
const state = () => {
const state = (): ImageViewerState => {
return {
modalOpen: false,
currentIndex: -1,

View File

@ -2,9 +2,9 @@ import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import i18n from '~/src/config/i18n'
import router from '@/router'
import Jump from '~/src/renderer/store/TimelineSpace/Modals/Jump'
import Jump, { JumpState, Channel } from '~/src/renderer/store/TimelineSpace/Modals/Jump'
const state = () => {
const state = (): JumpState => {
return {
modalOpen: true,
channel: '',
@ -93,10 +93,11 @@ describe('Jump', () => {
describe('jump', () => {
it('should be changed', () => {
store.dispatch('Jump/jump', {
const channel: Channel = {
name: 'public',
path: 'public'
})
}
store.dispatch('Jump/jump', channel)
expect(store.state.Jump.modalOpen).toEqual(false)
expect(router.push).toHaveBeenCalledWith({ path: '/0/public' })
})

View File

@ -1,11 +1,33 @@
import { Response, List } from 'megalodon'
import { Response, List, Account } from 'megalodon'
import mockedMegalodon from '~/spec/mock/megalodon'
import { createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
import ListMembership from '~/src/renderer/store/TimelineSpace/Modals/ListMembership'
import ListMembership, { ListMembershipState } from '@/store/TimelineSpace/Modals/ListMembership'
jest.mock('megalodon')
const account: 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 list1: List = {
id: 1,
title: 'list1'
@ -16,7 +38,7 @@ const list2: List = {
title: 'list2'
}
let state: any = () => {
let state = (): ListMembershipState => {
return {
modalOpen: false,
account: null,
@ -116,9 +138,7 @@ describe('ListMembership', () => {
state = () => {
return {
modalOpen: false,
account: {
id: 65
},
account: account,
lists: [],
belongToLists: [
list2

View File

@ -1,9 +1,11 @@
import i18n from '~/src/config/i18n'
import Jump from '@/store/TimelineSpace/Modals/Jump'
import Jump, { JumpState, MUTATION_TYPES, Channel } from '@/store/TimelineSpace/Modals/Jump'
import Hashtag from '~/src/types/hashtag'
import { List } from 'megalodon'
describe('TimelineSpace/Modals/Jump', () => {
describe('mutations', () => {
let state
let state: JumpState
beforeEach(() => {
state = {
modalOpen: true,
@ -53,59 +55,55 @@ describe('TimelineSpace/Modals/Jump', () => {
describe('updateListChannel', () => {
it('should be updated', () => {
const channelList = [
{
id: '0',
title: 'admin'
},
{
id: '1',
title: 'engineer'
},
{
id: '2',
title: 'designer'
}
]
Jump.mutations.updateListChannel(state, channelList)
expect(state.listChannelList).toEqual([
{
path: 'lists/0',
name: '#admin'
},
{
path: 'lists/1',
name: '#engineer'
},
{
path: 'lists/2',
name: '#designer'
}
])
const admin: List = {
id: 0,
title: 'admin'
}
const engineer: List = {
id: 1,
title: 'engineer'
}
const designer: 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 channelList = [
{
tagName: 'whalebird'
},
{
tagName: 'tqrk'
}
]
Jump.mutations.updateTagChannel(state, channelList)
expect(state.tagChannelList).toEqual([
{
name: '#whalebird',
path: 'hashtag/whalebird'
},
{
name: '#tqrk',
path: 'hashtag/tqrk'
}
])
const whalebird: Hashtag = {
tagName: 'whalebird'
}
const tqrk: Hashtag = {
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])
})
})
})

View File

@ -1,14 +1,22 @@
import General from './Preferences/General'
import Account from './Preferences/Account'
import Language from './Preferences/Language'
import Notification from './Preferences/Notification'
import Appearance from './Preferences/Appearance'
import General, { GeneralState } from './Preferences/General'
import Account, { AccountState } from './Preferences/Account'
import Language, { LanguageState } from './Preferences/Language'
import Appearance, { AppearanceState } from './Preferences/Appearance'
import Notification, { NotificationState } from './Preferences/Notification'
import { Module } from 'vuex'
export interface PreferencesState {}
const state = (): PreferencesState => ({})
export interface PreferencesModuleState extends PreferencesState {
General: GeneralState,
Account: AccountState,
Language: LanguageState,
Notification: NotificationState,
Appearance: AppearanceState
}
// TODO: use type of rootState
const Preferences: Module<PreferencesState, any> = {
namespaced: true,

View File

@ -1,5 +1,5 @@
import General from './Settings/General'
import Timeline from './Settings/Timeline'
import General, { GeneralState } from './Settings/General'
import Timeline, { TimelineState } from './Settings/Timeline'
import { Module, MutationTree } from 'vuex'
export interface SettingsState {
@ -20,6 +20,11 @@ const mutations: MutationTree<SettingsState> = {
}
}
export interface SettingsModuleState extends SettingsState {
General: GeneralState,
Timeline: TimelineState,
}
// TODO: use type of rootState
const Settings: Module<SettingsState, any> = {
namespaced: true,

View File

@ -1,9 +1,9 @@
import sanitizeHtml from 'sanitize-html'
import { ipcRenderer } from 'electron'
import Mastodon, { Account, Emoji, Instance, Status, Notification as NotificationType } from 'megalodon'
import SideMenu from './TimelineSpace/SideMenu'
import HeaderMenu from './TimelineSpace/HeaderMenu'
import Modals from './TimelineSpace/Modals'
import SideMenu, { SideMenuState } from './TimelineSpace/SideMenu'
import HeaderMenu, { HeaderMenuState } from './TimelineSpace/HeaderMenu'
import Modals, { ModalsModuleState } from './TimelineSpace/Modals'
import Contents from './TimelineSpace/Contents'
import router from '@/router'
import unreadSettings from '~/src/constants/unreadNotification'
@ -416,6 +416,13 @@ const actions: ActionTree<TimelineSpaceState, any> = {
}
}
export interface TimelineSpaceModuleState extends TimelineSpaceState {
SideMenu: SideMenuState,
HeaderMenu: HeaderMenuState,
Modals: ModalsModuleState
// TODO: Contents: ContentsState
}
const TimelineSpace: Module<TimelineSpaceState, any> = {
namespaced: true,
modules: {

View File

@ -1,37 +0,0 @@
import NewToot from './Modals/NewToot'
import ImageViewer from './Modals/ImageViewer'
import Jump from './Modals/Jump'
import ListMembership from './Modals/ListMembership'
import AddListMember from './Modals/AddListMember'
import MuteConfirm from './Modals/MuteConfirm'
import Shortcut from './Modals/Shortcut'
import Report from './Modals/Report'
const Modals = {
namespaced: true,
modules: {
ImageViewer,
NewToot,
Jump,
ListMembership,
AddListMember,
MuteConfirm,
Shortcut,
Report
},
getters: {
modalOpened: (state, getters, rootState) => {
const imageViewer = rootState.TimelineSpace.Modals.ImageViewer.modalOpen
const newToot = rootState.TimelineSpace.Modals.NewToot.modalOpen
const jump = rootState.TimelineSpace.Modals.Jump.modalOpen
const listMembership = rootState.TimelineSpace.Modals.ListMembership.modalOpen
const addListMember = rootState.TimelineSpace.Modals.AddListMember.modalOpen
const shortcut = rootState.TimelineSpace.Modals.Shortcut.modalOpen
const muteConfirm = rootState.TimelineSpace.Modals.MuteConfirm.modalOpen
const report = rootState.TimelineSpace.Modals.Report.modalOpen
return imageViewer || newToot || jump || listMembership || addListMember || shortcut || muteConfirm || report
}
}
}
export default Modals

View File

@ -0,0 +1,57 @@
import NewToot, { NewTootModuleState } from './Modals/NewToot'
import ImageViewer, { ImageViewerState } from './Modals/ImageViewer'
import Jump, { JumpState } from './Modals/Jump'
import ListMembership, { ListMembershipState } from './Modals/ListMembership'
import AddListMember, { AddListMemberState } from './Modals/AddListMember'
import MuteConfirm, { MuteConfirmState } from './Modals/MuteConfirm'
import Shortcut, { ShortcutState } from './Modals/Shortcut'
import Report, { ReportState } from './Modals/Report'
import { Module, GetterTree } from 'vuex'
import { RootState } from '@/store/index'
export interface ModalsState {}
export interface ModalsModuleState extends ModalsState {
Jump: JumpState,
AddListMember: AddListMemberState,
ImageViewer: ImageViewerState,
ListMembership: ListMembershipState,
MuteConfirm: MuteConfirmState,
NewToot: NewTootModuleState,
Report: ReportState,
Shortcut: ShortcutState
}
const state = (): ModalsState => ({})
const getters: GetterTree<ModalsState, RootState> = {
modalOpened: (_state, _getters, rootState) => {
const imageViewer = rootState.TimelineSpace.Modals.ImageViewer.modalOpen
const newToot = rootState.TimelineSpace.Modals.NewToot.modalOpen
const jump = rootState.TimelineSpace.Modals.Jump.modalOpen
const listMembership = rootState.TimelineSpace.Modals.ListMembership.modalOpen
const addListMember = rootState.TimelineSpace.Modals.AddListMember.modalOpen
const shortcut = rootState.TimelineSpace.Modals.Shortcut.modalOpen
const muteConfirm = rootState.TimelineSpace.Modals.MuteConfirm.modalOpen
const report = rootState.TimelineSpace.Modals.Report.modalOpen
return imageViewer || newToot || jump || listMembership || addListMember || shortcut || muteConfirm || report
}
}
const Modals: Module<ModalsState, RootState> = {
namespaced: true,
modules: {
ImageViewer,
NewToot,
Jump,
ListMembership,
AddListMember,
MuteConfirm,
Shortcut,
Report
},
state: state,
getters: getters
}
export default Modals

View File

@ -1,49 +0,0 @@
import Mastodon from 'megalodon'
export default {
namespaced: true,
state: {
modalOpen: false,
accounts: [],
targetListId: null
},
mutations: {
changeModal (state, value) {
state.modalOpen = value
},
updateAccounts (state, accounts) {
state.accounts = accounts
},
setListId (state, id) {
state.targetListId = id
}
},
actions: {
changeModal ({ commit }, value) {
commit('changeModal', value)
},
search ({ commit, rootState }, name) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.get('/accounts/search', {
q: name,
following: true
})
.then(res => {
commit('updateAccounts', res.data)
return res.data
})
},
add ({ state, rootState }, account) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.post(`/lists/${state.targetListId}/accounts`, {
account_ids: [account.id]
})
}
}
}

View File

@ -0,0 +1,70 @@
import Mastodon, { Account, Response } from 'megalodon'
import { Module, MutationTree, ActionTree } from 'vuex'
export interface AddListMemberState {
modalOpen: boolean,
accounts: Array<Account>,
targetListId: number | null
}
const state = (): AddListMemberState => ({
modalOpen: false,
accounts: [],
targetListId: null
})
export const MUTATION_TYPES = {
CHANGE_MODAL: 'changeModal',
UPDATE_ACCOUNTS: 'updateAccounts',
SET_LIST_ID: 'setListId'
}
const mutations: MutationTree<AddListMemberState> = {
[MUTATION_TYPES.CHANGE_MODAL]: (state, value: boolean) => {
state.modalOpen = value
},
[MUTATION_TYPES.UPDATE_ACCOUNTS]: (state, accounts: Array<Account>) => {
state.accounts = accounts
},
[MUTATION_TYPES.SET_LIST_ID]: (state, id: number) => {
state.targetListId = id
}
}
// TODO: use type of rootState
const actions: ActionTree<AddListMemberState, any> = {
changeModal: ({ commit }, value: boolean) => {
commit(MUTATION_TYPES.CHANGE_MODAL, value)
},
search: async ({ commit, rootState }, name: string): Promise<Array<Account>> => {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const res: Response<Array<Account>> = await client.get<Array<Account>>('/accounts/search', {
q: name,
following: true
})
commit(MUTATION_TYPES.UPDATE_ACCOUNTS, res.data)
return res.data
},
add: async ({ state, rootState }, account: Account): Promise<{}> => {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const res: Response<{}> = await client.post<{}>(`/lists/${state.targetListId}/accounts`, {
account_ids: [account.id]
})
return res.data
}
}
const AddListMember: Module<AddListMemberState, any> = {
namespaced: true,
state: state,
mutations: mutations,
actions: actions
}
export default AddListMember

View File

@ -1,78 +0,0 @@
const ImageViewer = {
namespaced: true,
state: {
modalOpen: false,
currentIndex: -1,
mediaList: [],
loading: false
},
mutations: {
changeModal (state, value) {
state.modalOpen = value
},
changeCurrentIndex (state, currentIndex) {
state.currentIndex = currentIndex
},
changeMedliaList (state, mediaList) {
state.mediaList = mediaList
},
incrementIndex (state) {
state.currentIndex++
},
decrementIndex (state) {
state.currentIndex--
},
loading (state, value) {
state.loading = value
}
},
actions: {
openModal ({ commit }, { currentIndex, mediaList }) {
commit('changeModal', true)
commit('changeCurrentIndex', currentIndex)
commit('changeMedliaList', mediaList)
commit('loading', true)
},
closeModal ({ commit }) {
commit('changeModal', false)
commit('changeCurrentIndex', -1)
commit('changeMedliaList', [])
commit('loading', false)
},
incrementIndex ({ commit }) {
commit('incrementIndex')
commit('loading', true)
},
decrementIndex ({ commit }) {
commit('decrementIndex')
commit('loading', true)
},
async loaded ({ commit }) {
commit('loading', false)
}
},
getters: {
imageURL (state) {
if (state.currentIndex >= 0) {
return state.mediaList[state.currentIndex].url
}
},
imageType (state) {
if (state.currentIndex >= 0) {
return state.mediaList[state.currentIndex].type
}
},
showLeft (state) {
const notFirst = (state.currentIndex > 0)
const isManyItem = (state.mediaList.length > 1)
return (notFirst && isManyItem)
},
showRight (state) {
const notLast = (state.currentIndex < (state.mediaList.length - 1))
const isManyItem = (state.mediaList.length > 1)
return (notLast && isManyItem)
}
}
}
export default ImageViewer

View File

@ -0,0 +1,108 @@
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
import { Attachment } from 'megalodon'
import { RootState } from '@/store'
export interface ImageViewerState {
modalOpen: boolean,
currentIndex: number,
mediaList: Array<Attachment>,
loading: boolean
}
const state = (): ImageViewerState => ({
modalOpen: false,
currentIndex: -1,
mediaList: [],
loading: false
})
export const MUTATION_TYPES = {
CHANGE_MODAL: 'changeModal',
CHANGE_CURRENT_INDEX: 'changeCurrentIndex',
CHANGE_MEDIA_LIST: 'changeMediaList',
INCREMENT_INDEX: 'incrementIndex',
DECREMENT_INDEX: 'decrementIndex',
CHANGE_LOADING: 'changeLoading'
}
const mutations: MutationTree<ImageViewerState> = {
[MUTATION_TYPES.CHANGE_MODAL]: (state, value: boolean) => {
state.modalOpen = value
},
[MUTATION_TYPES.CHANGE_CURRENT_INDEX]: (state, currentIndex: number) => {
state.currentIndex = currentIndex
},
[MUTATION_TYPES.CHANGE_MEDIA_LIST]: (state, mediaList: Array<Attachment>) => {
state.mediaList = mediaList
},
[MUTATION_TYPES.INCREMENT_INDEX]: (state) => {
state.currentIndex++
},
[MUTATION_TYPES.DECREMENT_INDEX]: (state) => {
state.currentIndex--
},
[MUTATION_TYPES.CHANGE_LOADING]: (state, value: boolean) => {
state.loading = value
}
}
const actions: ActionTree<ImageViewerState, RootState> = {
openModal: ({ commit }, { currentIndex, mediaList }) => {
commit(MUTATION_TYPES.CHANGE_MODAL, true)
commit(MUTATION_TYPES.CHANGE_CURRENT_INDEX, currentIndex as number)
commit(MUTATION_TYPES.CHANGE_MEDIA_LIST, mediaList as Array<Attachment>)
commit(MUTATION_TYPES.CHANGE_LOADING, true)
},
closeModal: ({ commit }) => {
commit(MUTATION_TYPES.CHANGE_MODAL, false)
commit(MUTATION_TYPES.CHANGE_CURRENT_INDEX, -1)
commit(MUTATION_TYPES.CHANGE_MEDIA_LIST, [])
commit(MUTATION_TYPES.CHANGE_LOADING, false)
},
incrementIndex: ({ commit }) => {
commit(MUTATION_TYPES.INCREMENT_INDEX)
commit(MUTATION_TYPES.CHANGE_LOADING, true)
},
decrementIndex: ({ commit }) => {
commit(MUTATION_TYPES.DECREMENT_INDEX)
commit(MUTATION_TYPES.CHANGE_LOADING, true)
},
loaded: ({ commit }) => {
commit(MUTATION_TYPES.CHANGE_LOADING, false)
}
}
const getters: GetterTree<ImageViewerState, RootState> = {
imageURL: (state): string | null => {
if (state.currentIndex >= 0) {
return state.mediaList[state.currentIndex].url
}
return null
},
imageType: (state): string | null => {
if (state.currentIndex >= 0) {
return state.mediaList[state.currentIndex].type
}
return null
},
showLeft: (state): boolean => {
const notFirst = (state.currentIndex > 0)
const isManyItem = (state.mediaList.length > 1)
return (notFirst && isManyItem)
},
showRight: (state): boolean => {
const notLast = (state.currentIndex < (state.mediaList.length - 1))
const isManyItem = (state.mediaList.length > 1)
return (notLast && isManyItem)
}
}
const ImageViewer: Module<ImageViewerState, RootState> = {
namespaced: true,
state: state,
mutations: mutations,
actions: actions,
getters: getters
}
export default ImageViewer

View File

@ -1,99 +0,0 @@
import router from '@/router'
import i18n from '~/src/config/i18n'
const Jump = {
namespaced: true,
state: {
modalOpen: false,
channel: '',
defaultChannelList: [
{
name: i18n.t('side_menu.home'),
path: 'home'
},
{
name: i18n.t('side_menu.notification'),
path: 'notifications'
},
{
name: i18n.t('side_menu.mention'),
path: 'mentions'
},
{
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'
}
},
mutations: {
changeModal (state, value) {
state.modalOpen = value
},
updateChannel (state, value) {
state.channel = value
},
changeSelected (state, value) {
state.selectedChannel = value
},
updateListChannel (state, list) {
state.listChannelList = list.map((l) => {
return {
name: `#${l.title}`,
path: `lists/${l.id}`
}
})
},
updateTagChannel (state, tags) {
state.tagChannelList = tags.map(t => {
return {
name: `#${t.tagName}`,
path: `hashtag/${t.tagName}`
}
})
}
},
actions: {
jumpCurrentSelected ({ state, commit, rootState }) {
commit('changeModal', false)
router.push({ path: `/${rootState.TimelineSpace.account._id}/${state.selectedChannel.path}` })
},
jump ({ commit, rootState }, channel) {
commit('changeModal', false)
router.push({ path: `/${rootState.TimelineSpace.account._id}/${channel.path}` })
},
syncListChannel ({ commit, rootState }) {
commit('updateListChannel', rootState.TimelineSpace.SideMenu.lists)
},
syncTagChannel ({ commit, rootState }) {
commit('updateTagChannel', rootState.TimelineSpace.SideMenu.tags)
}
}
}
export default Jump

View File

@ -0,0 +1,133 @@
import router from '@/router'
import i18n from '~/src/config/i18n'
import { Module, MutationTree, ActionTree } from 'vuex'
import { List } from 'megalodon'
import Hashtag from '~/src/types/hashtag'
export interface Channel {
name: string,
path: string
}
export interface JumpState {
modalOpen: boolean,
channel: string,
defaultChannelList: Array<Channel>,
listChannelList: Array<Channel>,
tagChannelList: Array<Channel>,
selectedChannel: Channel
}
const state = (): JumpState => ({
modalOpen: false,
channel: '',
defaultChannelList: [
{
name: i18n.t('side_menu.home'),
path: 'home'
},
{
name: i18n.t('side_menu.notification'),
path: 'notifications'
},
{
name: i18n.t('side_menu.mention'),
path: 'mentions'
},
{
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'
}
})
export const MUTATION_TYPES = {
CHANGE_MODAL: 'changeModal',
UPDATE_CHANNEL: 'updateChannel',
CHANGE_SELECTED: 'changeSelected',
UPDATE_LIST_CHANNEL: 'updateListChannel',
UPDATE_TAG_CHANNEL: 'updateTagChannel'
}
const mutations: MutationTree<JumpState> = {
[MUTATION_TYPES.CHANGE_MODAL]: (state, value: boolean) => {
state.modalOpen = value
},
[MUTATION_TYPES.UPDATE_CHANNEL]: (state, channel: string) => {
state.channel = channel
},
[MUTATION_TYPES.CHANGE_SELECTED]: (state, channel: Channel) => {
state.selectedChannel = channel
},
[MUTATION_TYPES.UPDATE_LIST_CHANNEL]: (state, lists: Array<List>) => {
state.listChannelList = lists.map((l) => {
const channel: Channel = {
name: `#${l.title}`,
path: `lists/${l.id}`
}
return channel
})
},
[MUTATION_TYPES.UPDATE_TAG_CHANNEL]: (state, tags: Array<Hashtag>) => {
state.tagChannelList = tags.map(t => {
const channel: Channel = {
name: `#${t.tagName}`,
path: `hashtag/${t.tagName}`
}
return channel
})
}
}
// TODO: use type of rootState
const actions: ActionTree<JumpState, any> = {
jumpCurrentSelected: ({ state, commit, rootState }) => {
commit(MUTATION_TYPES.CHANGE_MODAL, false)
router.push({ path: `/${rootState.TimelineSpace.account._id}/${state.selectedChannel.path}` })
},
jump: ({ commit, rootState }, channel: Channel) => {
commit(MUTATION_TYPES.CHANGE_MODAL, false)
router.push({ path: `/${rootState.TimelineSpace.account._id}/${channel.path}` })
},
syncListChannel: ({ commit, rootState }) => {
commit(MUTATION_TYPES.UPDATE_LIST_CHANNEL, rootState.TimelineSpace.SideMenu.lists)
},
syncTagChannel: ({ commit, rootState }) => {
commit(MUTATION_TYPES.UPDATE_TAG_CHANNEL, rootState.TimelineSpace.SideMenu.tags)
}
}
const Jump: Module<JumpState, any> = {
namespaced: true,
state: state,
mutations: mutations,
actions: actions
}
export default Jump

View File

@ -1,80 +0,0 @@
import Mastodon from 'megalodon'
import _ from 'lodash'
const ListMembership = {
namespaced: true,
state: {
modalOpen: false,
account: null,
lists: [],
belongToLists: []
},
mutations: {
changeModal (state, value) {
state.modalOpen = value
},
changeAccount (state, account) {
state.account = account
},
changeBelongToLists (state, lists) {
state.belongToLists = lists
},
changeLists (state, lists) {
state.lists = lists
}
},
actions: {
changeModal ({ commit }, value) {
commit('changeModal', value)
},
setAccount ({ commit }, account) {
commit('changeAccount', account)
},
fetchListMembership ({ commit, rootState }, account) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.get(`/accounts/${account.id}/lists`)
.then(res => {
commit('changeBelongToLists', res.data.map(l => l.id))
return res.data
})
},
fetchLists ({ commit, rootState }) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.get('/lists')
.then(res => {
commit('changeLists', res.data)
return res.data
})
},
async changeBelongToLists ({ rootState, commit, state }, belongToLists) {
// Calcurate diff
const removedLists = _.difference(state.belongToLists, belongToLists)
const addedLists = _.difference(belongToLists, state.belongToLists)
commit('changeBelongToLists', belongToLists)
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const removedPromise = removedLists.map(id => {
return client.del(`/lists/${id}/accounts`, {
account_ids: [state.account.id]
})
})
const addedPromise = addedLists.map(id => {
return client.post(`/lists/${id}/accounts`, {
account_ids: [state.account.id]
})
})
const res = await Promise.all(removedPromise.concat(addedPromise))
return res
}
}
}
export default ListMembership

View File

@ -0,0 +1,98 @@
import Mastodon, { Account, List, Response } from 'megalodon'
import lodash from 'lodash'
import { Module, MutationTree, ActionTree } from 'vuex'
import { RootState } from '@/store'
export interface ListMembershipState {
modalOpen: boolean,
account: Account | null,
lists: Array<List>,
belongToLists: Array<List>
}
const state = (): ListMembershipState => ({
modalOpen: false,
account: null,
lists: [],
belongToLists: []
})
export const MUTATION_TYPES = {
CHANGE_MODAL: 'changeModal',
CHANGE_ACCOUNT: 'changeAccount',
CHANGE_BELONG_TO_LISTS: 'changeBelongToLists',
CHANGE_LISTS: 'changeLists'
}
const mutations: MutationTree<ListMembershipState> = {
[MUTATION_TYPES.CHANGE_MODAL]: (state, value: boolean) => {
state.modalOpen = value
},
[MUTATION_TYPES.CHANGE_ACCOUNT]: (state, account: Account) => {
state.account = account
},
[MUTATION_TYPES.CHANGE_BELONG_TO_LISTS]: (state, lists: Array<List>) => {
state.belongToLists = lists
},
[MUTATION_TYPES.CHANGE_LISTS]: (state, lists: Array<List>) => {
state.lists = lists
}
}
const actions: ActionTree<ListMembershipState, RootState> = {
changeModal: ({ commit }, value: boolean) => {
commit(MUTATION_TYPES.CHANGE_MODAL, value)
},
setAccount: ({ commit }, account: Account) => {
commit(MUTATION_TYPES.CHANGE_ACCOUNT, account)
},
fetchListMembership: async ({ commit, rootState }, account: Account) => {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken!,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const res: Response<Array<List>> = await client.get<Array<List>>(`/accounts/${account.id}/lists`)
commit(MUTATION_TYPES.CHANGE_BELONG_TO_LISTS, res.data.map(l => l.id))
return res.data
},
fetchLists: async ({ commit, rootState }) => {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken!,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const res: Response<Array<List>> = await client.get<Array<List>>('/lists')
commit(MUTATION_TYPES.CHANGE_LISTS, res.data)
return res.data
},
changeBelongToLists: async ({ rootState, commit, state }, belongToLists: Array<List>) => {
// Calcurate diff
const removedLists = lodash.difference(state.belongToLists, belongToLists)
const addedLists = lodash.difference(belongToLists, state.belongToLists)
commit(MUTATION_TYPES.CHANGE_BELONG_TO_LISTS, belongToLists)
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken!,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const removedPromise = removedLists.map(id => {
return client.del<{}>(`/lists/${id}/accounts`, {
account_ids: [state.account!.id]
})
})
const addedPromise = addedLists.map(id => {
return client.post<{}>(`/lists/${id}/accounts`, {
account_ids: [state.account!.id]
})
})
const res = await Promise.all(removedPromise.concat(addedPromise))
return res
}
}
const ListMembership: Module<ListMembershipState, RootState> = {
namespaced: true,
state: state,
mutations: mutations,
actions: actions
}
export default ListMembership

View File

@ -1,39 +0,0 @@
import Mastodon from 'megalodon'
export default {
namespaced: true,
state: {
modalOpen: false,
account: {}
},
mutations: {
changeModal (state, value) {
state.modalOpen = value
},
changeAccount (state, account) {
state.account = account
}
},
actions: {
changeModal ({ commit }, value) {
commit('changeModal', value)
},
changeAccount ({ commit }, account) {
commit('changeAccount', account)
},
async submit ({ state, rootState, dispatch }, notify) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.post(`/accounts/${state.account.id}/mute`, {
notifications: notify
})
.then(res => {
// Reload relationship
dispatch('TimelineSpace/Contents/SideBar/AccountProfile/fetchRelationship', state.account, { root: true })
return res.data
})
}
}
}

View File

@ -0,0 +1,57 @@
import Mastodon, { Account, Response, Relationship } from 'megalodon'
import { Module, MutationTree, ActionTree } from 'vuex'
import { RootState } from '@/store'
export interface MuteConfirmState {
modalOpen: boolean,
account: Account | null
}
const state = (): MuteConfirmState => ({
modalOpen: false,
account: null
})
export const MUTATION_TYPES = {
CHANGE_MODAL: 'changeModal',
CHANGE_ACCOUNT: 'changeAccount'
}
const mutations: MutationTree<MuteConfirmState> = {
[MUTATION_TYPES.CHANGE_MODAL]: (state, value: boolean) => {
state.modalOpen = value
},
[MUTATION_TYPES.CHANGE_ACCOUNT]: (state, account: Account) => {
state.account = account
}
}
const actions: ActionTree<MuteConfirmState, RootState> = {
changeModal: ({ commit }, value: boolean) => {
commit(MUTATION_TYPES.CHANGE_MODAL, value)
},
changeAccount: ({ commit }, account: Account) => {
commit(MUTATION_TYPES.CHANGE_ACCOUNT, account)
},
submit: async ({ state, rootState, dispatch }, notify: boolean) => {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken!,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const res: Response<Relationship> = await client.post<Relationship>(`/accounts/${state.account!.id}/mute`, {
notifications: notify
})
// Reload relationship
dispatch('TimelineSpace/Contents/SideBar/AccountProfile/fetchRelationship', state.account, { root: true })
return res.data
}
}
const MuteConfirm: Module<MuteConfirmState, RootState> = {
namespaced: true,
state: state,
mutations: mutations,
actions: actions
}
export default MuteConfirm

View File

@ -1,204 +0,0 @@
import Mastodon from 'megalodon'
import { ipcRenderer } from 'electron'
import Visibility from '~/src/constants/visibility'
import Status from './NewToot/Status'
const NewToot = {
namespaced: true,
modules: {
Status
},
state: {
modalOpen: false,
initialStatus: '',
initialSpoiler: '',
replyToMessage: null,
blockSubmit: false,
attachedMedias: [],
visibility: Visibility.Public.value,
sensitive: false,
attachedMediaId: 0,
pinedHashtag: false,
hashtags: []
},
mutations: {
changeModal (state, value) {
state.modalOpen = value
},
setReplyTo (state, message) {
state.replyToMessage = message
},
updateInitialStatus (state, status) {
state.initialStatus = status
},
updateInitialSpoiler (state, cw) {
state.initialSpoiler = cw
},
changeBlockSubmit (state, value) {
state.blockSubmit = value
},
appendAttachedMedias (state, media) {
state.attachedMedias = state.attachedMedias.concat([media])
},
clearAttachedMedias (state) {
state.attachedMedias = []
},
removeMedia (state, media) {
state.attachedMedias = state.attachedMedias.filter(m => m.id !== media.id)
},
/**
* changeVisibilityValue
* Update visibility using direct value
* @param state vuex state object
* @param value visibility value
*/
changeVisibilityValue (state, value) {
state.visibility = value
},
changeSensitive (state, value) {
state.sensitive = value
},
updateMediaId (state, value) {
state.attachedMediaId = value
},
changePinedHashtag (state, value) {
state.pinedHashtag = value
},
updateHashtags (state, tags) {
state.hashtags = tags
}
},
getters: {
hashtagInserting (state) {
return !state.replyToMessage && state.pinedHashtag
}
},
actions: {
async updateMedia ({ rootState }, media) {
if (rootState.TimelineSpace.account.accessToken === undefined || rootState.TimelineSpace.account.accessToken === null) {
throw new AuthenticationError()
}
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return Promise.all(
Object.keys(media).map(async id => {
return client.put(`/media/${id}`, { description: media[id] })
}
)).catch(err => {
console.error(err)
throw err
})
},
async postToot ({ state, commit, rootState }, form) {
if (rootState.TimelineSpace.account.accessToken === undefined || rootState.TimelineSpace.account.accessToken === null) {
throw new AuthenticationError()
}
if (state.blockSubmit) {
return
}
commit('changeBlockSubmit', true)
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.post('/statuses', form)
.then(res => {
ipcRenderer.send('toot-action-sound')
return res.data
})
.finally(() => {
commit('changeBlockSubmit', false)
})
},
openReply ({ commit, rootState }, message) {
commit('setReplyTo', message)
const mentionAccounts = [message.account.acct].concat(message.mentions.map(a => a.acct))
.filter((a, i, self) => self.indexOf(a) === i)
.filter((a) => a !== rootState.TimelineSpace.account.username)
commit('updateInitialStatus', `${mentionAccounts.map(m => `@${m}`).join(' ')} `)
commit('updateInitialSpoiler', message.spoiler_text)
commit('changeModal', true)
let value = Visibility.Public.value
Object.keys(Visibility).map(key => {
const target = Visibility[key]
if (target.key === message.visibility) {
value = target.value
}
})
commit('changeVisibilityValue', value)
},
openModal ({ dispatch, commit, state }) {
if (!state.replyToMessage && state.pinedHashtag) {
commit('updateInitialStatus', state.hashtags.map(t => ` #${t.name}`).join())
}
commit('changeModal', true)
dispatch('fetchVisibility')
},
closeModal ({ commit }) {
commit('changeModal', false)
commit('updateInitialStatus', '')
commit('updateInitialSpoiler', '')
commit('setReplyTo', null)
commit('changeBlockSubmit', false)
commit('clearAttachedMedias')
commit('changeSensitive', false)
commit('changeVisibilityValue', Visibility.Public.value)
},
uploadImage ({ commit, rootState }, image) {
commit('changeBlockSubmit', true)
if (rootState.TimelineSpace.account.accessToken === undefined || rootState.TimelineSpace.account.accessToken === null) {
throw new AuthenticationError()
}
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const formData = new FormData()
formData.append('file', image)
return client.post('/media', formData)
.then(res => {
commit('changeBlockSubmit', false)
if (res.data.type === 'unknown') throw new UnknownTypeError()
commit('appendAttachedMedias', res.data)
return res.data
})
.catch(err => {
commit('changeBlockSubmit', false)
console.error(err)
throw err
})
},
incrementMediaId ({ commit, state }) {
commit('updateMediaId', state.attachedMediaId + 1)
},
resetMediaId ({ commit }) {
commit('updateMediaId', 0)
},
updateHashtags ({ commit, state }, tags) {
if (state.pinedHashtag) {
commit('updateHashtags', tags)
}
},
fetchVisibility ({ commit, rootState }) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.get('/accounts/verify_credentials')
.then(res => {
const visibility = Object.values(Visibility).find((v) => {
return v.key === res.data.source.privacy
})
commit('changeVisibilityValue', visibility.value)
return res.data
})
}
}
}
export default NewToot
class AuthenticationError {}
class UnknownTypeError {}

View File

@ -0,0 +1,250 @@
import Mastodon, { Status, Attachment, Tag, Response, Account } from 'megalodon'
import { ipcRenderer } from 'electron'
import Visibility from '~/src/constants/visibility'
import TootStatus, { StatusState } from './NewToot/Status'
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
import { RootState } from '@/store'
import VisibilityType from '~/src/types/visibility'
export interface NewTootState {
modalOpen: boolean,
initialStatus: string,
initialSpoiler: string,
replyToMessage: Status | null,
blockSubmit: boolean,
attachedMedias: Array<Attachment>,
visibility: number,
sensitive: boolean,
attachedMediaId: number,
pinedHashtag: boolean,
hashtags: Array<Tag>
}
export interface NewTootModuleState extends NewTootState {
Status: StatusState
}
const state = (): NewTootState => ({
modalOpen: false,
initialStatus: '',
initialSpoiler: '',
replyToMessage: null,
blockSubmit: false,
attachedMedias: [],
visibility: Visibility.Public.value,
sensitive: false,
attachedMediaId: 0,
pinedHashtag: false,
hashtags: []
})
export const MUTATION_TYPES = {
CHANGE_MODAL: 'changeModal',
SET_REPLY_TO: 'setReplyTo',
UPDATE_INITIAL_STATUS: 'updateInitialStatus',
UPDATE_INITIAL_SPOILER: 'updateInitialSpoiler',
CHANGE_BLOCK_SUBMIT: 'changeBlockSubmit',
APPEND_ATTACHED_MEDIAS: 'appendAttachedMedias',
CLEAR_ATTACHED_MEDIAS: 'clearAttachedMedias',
REMOVE_MEDIA: 'removeMedia',
CHANGE_VISIBILITY_VALUE: 'changeVisibilityValue',
CHANGE_SENSITIVE: 'changeSensitive',
UPDATE_MEDIA_ID: 'updateMediaId',
CHANGE_PINED_HASHTAG: 'changePinedHashtag',
UPDATE_HASHTAGS: 'updateHashtags'
}
const mutations: MutationTree<NewTootState> = {
[MUTATION_TYPES.CHANGE_MODAL]: (state, value: boolean) => {
state.modalOpen = value
},
[MUTATION_TYPES.SET_REPLY_TO]: (state, message: Status) => {
state.replyToMessage = message
},
[MUTATION_TYPES.UPDATE_INITIAL_STATUS]: (state, status: string) => {
state.initialStatus = status
},
[MUTATION_TYPES.UPDATE_INITIAL_SPOILER]: (state, cw: string) => {
state.initialSpoiler = cw
},
[MUTATION_TYPES.CHANGE_BLOCK_SUBMIT]: (state, value: boolean) => {
state.blockSubmit = value
},
[MUTATION_TYPES.APPEND_ATTACHED_MEDIAS]: (state, media: Attachment) => {
state.attachedMedias = state.attachedMedias.concat([media])
},
[MUTATION_TYPES.CLEAR_ATTACHED_MEDIAS]: (state) => {
state.attachedMedias = []
},
[MUTATION_TYPES.REMOVE_MEDIA]: (state, media: Attachment) => {
state.attachedMedias = state.attachedMedias.filter(m => m.id !== media.id)
},
/**
* changeVisibilityValue
* Update visibility using direct value
* @param state vuex state object
* @param value visibility value
*/
[MUTATION_TYPES.CHANGE_VISIBILITY_VALUE]: (state, value: number) => {
state.visibility = value
},
[MUTATION_TYPES.CHANGE_SENSITIVE]: (state, value: boolean) => {
state.sensitive = value
},
[MUTATION_TYPES.UPDATE_MEDIA_ID]: (state, value: number) => {
state.attachedMediaId = value
},
[MUTATION_TYPES.CHANGE_PINED_HASHTAG]: (state, value: boolean) => {
state.pinedHashtag = value
},
[MUTATION_TYPES.UPDATE_HASHTAGS]: (state, tags: Array<Tag>) => {
state.hashtags = tags
}
}
const actions: ActionTree<NewTootState, RootState> = {
updateMedia: async ({ rootState }, media: Attachment) => {
if (rootState.TimelineSpace.account.accessToken === undefined || rootState.TimelineSpace.account.accessToken === null) {
throw new AuthenticationError()
}
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const attachments = Object.keys(media).map(async id => {
return client.put<Attachment>(`/media/${id}`, { description: media[id] })
})
return Promise.all(attachments)
.catch(err => {
console.error(err)
throw err
})
},
postToot: async ({ state, commit, rootState }, form) => {
if (rootState.TimelineSpace.account.accessToken === undefined || rootState.TimelineSpace.account.accessToken === null) {
throw new AuthenticationError()
}
if (state.blockSubmit) {
return
}
commit(MUTATION_TYPES.CHANGE_BLOCK_SUBMIT, true)
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.post<Status>('/statuses', form)
.then((res: Response<Status>) => {
ipcRenderer.send('toot-action-sound')
return res.data
})
.finally(() => {
commit(MUTATION_TYPES.CHANGE_BLOCK_SUBMIT, false)
})
},
openReply: ({ commit, rootState }, message: Status) => {
commit(MUTATION_TYPES.SET_REPLY_TO, message)
const mentionAccounts = [message.account.acct].concat(message.mentions.map(a => a.acct))
.filter((a, i, self) => self.indexOf(a) === i)
.filter((a) => a !== rootState.TimelineSpace.account.username)
commit(MUTATION_TYPES.UPDATE_INITIAL_STATUS, `${mentionAccounts.map(m => `@${m}`).join(' ')} `)
commit(MUTATION_TYPES.UPDATE_INITIAL_SPOILER, message.spoiler_text)
commit(MUTATION_TYPES.CHANGE_MODAL, true)
let value: number = Visibility.Public.value
Object.keys(Visibility).map(key => {
const target = Visibility[key]
if (target.key === message.visibility) {
value = target.value
}
})
commit(MUTATION_TYPES.CHANGE_VISIBILITY_VALUE, value)
},
openModal: ({ dispatch, commit, state }) => {
if (!state.replyToMessage && state.pinedHashtag) {
commit(MUTATION_TYPES.UPDATE_INITIAL_STATUS, state.hashtags.map(t => ` #${t.name}`).join())
}
commit(MUTATION_TYPES.CHANGE_MODAL, true)
dispatch('fetchVisibility')
},
closeModal: ({ commit }) => {
commit(MUTATION_TYPES.CHANGE_MODAL, false)
commit(MUTATION_TYPES.UPDATE_INITIAL_STATUS, '')
commit(MUTATION_TYPES.UPDATE_INITIAL_SPOILER, '')
commit(MUTATION_TYPES.SET_REPLY_TO, null)
commit(MUTATION_TYPES.CHANGE_BLOCK_SUBMIT, false)
commit(MUTATION_TYPES.CLEAR_ATTACHED_MEDIAS)
commit(MUTATION_TYPES.CHANGE_SENSITIVE, false)
commit(MUTATION_TYPES.CHANGE_VISIBILITY_VALUE, Visibility.Public.value)
},
uploadImage: async ({ commit, rootState }, image: any) => {
commit(MUTATION_TYPES.CHANGE_BLOCK_SUBMIT, true)
if (rootState.TimelineSpace.account.accessToken === undefined || rootState.TimelineSpace.account.accessToken === null) {
throw new AuthenticationError()
}
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const formData = new FormData()
formData.append('file', image)
return client.post<Attachment>('/media', formData)
.then(res => {
commit(MUTATION_TYPES.CHANGE_BLOCK_SUBMIT, false)
if (res.data.type === 'unknown') throw new UnknownTypeError()
commit(MUTATION_TYPES.APPEND_ATTACHED_MEDIAS, res.data)
return res.data
})
.catch(err => {
commit(MUTATION_TYPES.CHANGE_BLOCK_SUBMIT, false)
console.error(err)
throw err
})
},
incrementMediaId: ({ commit, state }) => {
commit(MUTATION_TYPES.UPDATE_MEDIA_ID, state.attachedMediaId + 1)
},
resetMediaId: ({ commit }) => {
commit(MUTATION_TYPES.UPDATE_MEDIA_ID, 0)
},
updateHashtags: ({ commit, state }, tags: Array<Tag>) => {
if (state.pinedHashtag) {
commit(MUTATION_TYPES.UPDATE_HASHTAGS, tags)
}
},
fetchVisibility: async ({ commit, rootState }) => {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken!,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const res: Response<Account> = await client.get<Account>('/accounts/verify_credentials')
const visibility: VisibilityType = Object.values(Visibility as Array<Visibility>).find((v) => {
return v.key === res.data.source!.privacy
})
if (visibility === undefined) {
throw new Error('Visibility value is invalid')
}
commit(MUTATION_TYPES.CHANGE_VISIBILITY_VALUE, visibility.value)
return res.data
}
}
const getters: GetterTree<NewTootState, RootState> = {
hashtagInserting: (state) => {
return !state.replyToMessage && state.pinedHashtag
}
}
const NewToot: Module<NewTootState, RootState> = {
namespaced: true,
modules: {
Status: TootStatus
},
state: state,
mutations: mutations,
getters: getters,
actions: actions
}
export default NewToot
class AuthenticationError {}
class UnknownTypeError {}

View File

@ -1,73 +0,0 @@
import Mastodon from 'megalodon'
const Status = {
namespaced: true,
state: {
filteredAccounts: [],
filteredHashtags: []
},
mutations: {
updateFilteredAccounts (state, accounts) {
state.filteredAccounts = accounts.map((a) => {
return {
name: `@${a.acct}`,
image: null
}
})
},
clearFilteredAccounts (state) {
state.filteredAccounts = []
},
updateFilteredHashtags (state, tags) {
state.filteredHashtags = tags.map((t) => {
return {
name: `#${t}`,
image: null
}
})
},
clearFilteredHashtags (state) {
state.filteredHashtags = []
}
},
actions: {
async searchAccount ({ commit, rootState }, word) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const res = await client.get('/search', { q: word, resolve: false })
commit('updateFilteredAccounts', res.data.accounts)
if (res.data.accounts.length === 0) throw new Error('Empty')
return res.data.accounts
},
async searchHashtag ({ commit, rootState }, word) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const res = await client.get('/search', { q: word })
commit('updateFilteredHashtags', res.data.hashtags)
if (res.data.hashtags.length === 0) throw new Error('Empty')
return res.data.hashtags
}
},
getters: {
pickerEmojis: (state, getters, rootState) => {
return rootState.TimelineSpace.emojis.filter((e, i, array) => {
return (array.findIndex(ar => e.name === ar.name) === i)
}).map(e => {
return {
name: e.name,
short_names: [e.name],
text: e.name,
emoticons: [],
keywords: [e.name],
imageUrl: e.image
}
})
}
}
}
export default Status

View File

@ -0,0 +1,100 @@
import Mastodon, { Account, Tag, Response, Results } from 'megalodon'
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
import { RootState } from '@/store/index'
interface Suggest {
name: string,
image: string | null
}
interface SuggestAccount extends Suggest {}
interface SuggestHashtag extends Suggest {}
export interface StatusState {
filteredAccounts: Array<SuggestAccount>,
filteredHashtags: Array<SuggestHashtag>
}
const state = (): StatusState => ({
filteredAccounts: [],
filteredHashtags: []
})
export const MUTATION_TYPES = {
UPDATE_FILTERED_ACCOUNTS: 'updateFilteredAccounts',
CLEAR_FILTERED_ACCOUNTS: 'clearFilteredAccounts',
UPDATE_FILTERED_HASHTAGS: 'updateFilteredHashtags',
CLAER_FILTERED_HASHTAGS: 'clearFilteredHashtags'
}
const mutations: MutationTree<StatusState> = {
[MUTATION_TYPES.UPDATE_FILTERED_ACCOUNTS]: (state, accounts: Array<Account>) => {
state.filteredAccounts = accounts.map(a => ({
name: `@${a.acct}`,
image: null
}))
},
[MUTATION_TYPES.CLEAR_FILTERED_ACCOUNTS]: (state) => {
state.filteredAccounts = []
},
[MUTATION_TYPES.UPDATE_FILTERED_HASHTAGS]: (state, tags: Array<Tag>) => {
state.filteredHashtags = tags.map(t => ({
name: `#${t}`,
image: null
}))
},
[MUTATION_TYPES.CLEAR_FILTERED_ACCOUNTS]: (state) => {
state.filteredHashtags = []
}
}
const actions: ActionTree<StatusState, RootState> = {
searchAccount: async ({ commit, rootState }, word: string) => {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken!,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const res: Response<Results> = await client.get<Results>('/search', { q: word, resolve: false })
commit(MUTATION_TYPES.UPDATE_FILTERED_ACCOUNTS, res.data.accounts)
if (res.data.accounts.length === 0) throw new Error('Empty')
return res.data.accounts
},
searchHashtag: async ({ commit, rootState }, word: string) => {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken!,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
const res: Response<Results> = await client.get<Results>('/search', { q: word })
commit(MUTATION_TYPES.UPDATE_FILTERED_HASHTAGS, res.data.hashtags)
if (res.data.hashtags.length === 0) throw new Error('Empty')
return res.data.hashtags
}
}
const getters: GetterTree<StatusState, RootState> = {
pickerEmojis: (_state, _getters, rootState) => {
return rootState.TimelineSpace.emojis.filter((e, i, array) => {
return (array.findIndex(ar => e.name === ar.name) === i)
}).map(e => {
return {
name: e.name,
short_names: [e.name],
text: e.name,
emoticons: [],
keywords: [e.name],
imageUrl: e.image
}
})
}
}
const Status: Module<StatusState, RootState> = {
namespaced: true,
state: state,
mutations: mutations,
actions: actions,
getters: getters
}
export default Status

View File

@ -1,34 +0,0 @@
import Mastodon from 'megalodon'
export default {
namespaced: true,
state: {
modalOpen: false,
message: {}
},
mutations: {
changeModalOpen (state, value) {
state.modalOpen = value
},
changeMessage (state, value) {
state.message = value
}
},
actions: {
openReport ({ commit }, message) {
commit('changeMessage', message)
commit('changeModalOpen', true)
},
submit ({ rootState }, payload) {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.post(`/reports`, {
account_id: payload.account_id,
status_ids: [payload.status_id],
comment: payload.comment
})
}
}
}

View File

@ -0,0 +1,52 @@
import Mastodon, { Status } from 'megalodon'
import { Module, MutationTree, ActionTree } from 'vuex'
import { RootState } from '@/store'
export interface ReportState {
modalOpen: boolean,
message: Status | null
}
const state = (): ReportState => ({
modalOpen: false,
message: null
})
export const MUTATION_TYPES = {
CHANGE_MODAL_OPEN: 'changeModalOpen',
CHANGE_MESSAGE: 'changeMessage'
}
const mutations: MutationTree<ReportState> = {
[MUTATION_TYPES.CHANGE_MODAL_OPEN]: (state, value: boolean) => {
state.modalOpen = value
},
[MUTATION_TYPES.CHANGE_MESSAGE]: (state, message: Status) => {
state.message = message
}
}
const actions: ActionTree<ReportState, RootState> = {
openReport: ({ commit }, message: Status) => {
commit(MUTATION_TYPES.CHANGE_MESSAGE, message)
commit(MUTATION_TYPES.CHANGE_MODAL_OPEN, true)
},
submit: async ({ rootState }, { account_id, status_id, comment }) => {
const client = new Mastodon(
rootState.TimelineSpace.account.accessToken!,
rootState.TimelineSpace.account.baseURL + '/api/v1'
)
return client.post<{}>(`/reports`, {
account_id: account_id,
status_ids: [status_id],
comment: comment
})
}
}
export default {
namespaced: true,
state: state,
mutations: mutations,
actions: actions
} as Module<ReportState, RootState>

View File

@ -1,11 +0,0 @@
export default {
namespaced: true,
state: {
modalOpen: false
},
mutations: {
changeModal (state, value) {
state.modalOpen = value
}
}
}

View File

@ -0,0 +1,26 @@
import { Module, MutationTree } from 'vuex'
import { RootState } from '@/store'
export interface ShortcutState {
modalOpen: boolean
}
const state = (): ShortcutState => ({
modalOpen: false
})
export const MUTATION_TYPES = {
CHANGE_MODAL: 'changeModal'
}
const mutations: MutationTree<ShortcutState> = {
[MUTATION_TYPES.CHANGE_MODAL]: (state, value: boolean) => {
state.modalOpen = value
}
}
export default {
namespaced: true,
state: state,
mutations: mutations
} as Module<ShortcutState, RootState>

View File

@ -2,17 +2,28 @@ import Vue from 'vue'
import Vuex from 'vuex'
import createLogger from 'vuex/dist/logger'
import App from './App'
import GlobalHeader from './GlobalHeader'
import Login from './Login'
import Authorize from './Authorize'
import TimelineSpace from './TimelineSpace'
import Preferences from './Preferences'
import Settings from './Settings'
import molecules from './molecules'
import App, { AppState } from './App'
import GlobalHeader, { GlobalHeaderState } from './GlobalHeader'
import Login, { LoginState } from './Login'
import Authorize, { AuthorizeState } from './Authorize'
import TimelineSpace, { TimelineSpaceModuleState } from './TimelineSpace'
import Preferences, { PreferencesModuleState } from './Preferences'
import Settings, { SettingsModuleState } from './Settings'
import molecules, { MoleculesModuleState } from './molecules'
Vue.use(Vuex)
export interface RootState {
App: AppState,
GlobalHeader: GlobalHeaderState,
Login: LoginState,
Authorize: AuthorizeState,
TimelineSpace: TimelineSpaceModuleState,
Preferences: PreferencesModuleState,
Settings: SettingsModuleState,
molecules: MoleculesModuleState
}
export default new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
plugins: process.env.NODE_ENV !== 'production'

View File

@ -1,5 +1,8 @@
import Toot from './molecules/Toot'
export interface MoleculesModuleState {
}
export default {
namespaced: true,
modules: {

View File

@ -2,7 +2,11 @@
"compilerOptions": {
"target": "es5",
"module": "es2015",
"lib": ["es6"],
"lib": [
"dom",
"dom.iterable",
"es6"
],
"sourceMap": true,
"downlevelIteration": true,
"strict": true,