refs #850 Replace NewToot and Status with typescript

This commit is contained in:
AkiraFukushima 2019-04-11 00:06:43 +09:00
parent 20f3dcba22
commit 0208e7c22a
6 changed files with 361 additions and 280 deletions

View File

@ -1,4 +1,4 @@
import NewToot from './Modals/NewToot'
import NewToot, { NewTootModuleState } from './Modals/NewToot'
import ImageViewer, { ImageViewerState } from './Modals/ImageViewer'
import Jump, { JumpState } from './Modals/Jump'
import ListMembership, { ListMembershipState } from './Modals/ListMembership'
@ -16,7 +16,8 @@ export interface ModalsModuleState extends ModalsState {
AddListMember: AddListMemberState,
ImageViewer: ImageViewerState,
ListMembership: ListMembershipState,
MuteConfirm: MuteConfirmState
MuteConfirm: MuteConfirmState,
NewToot: NewTootModuleState
}
const state = (): ModalsState => ({})

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,249 @@
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'
)
return Promise.all(
Object.keys(media).map(async id => {
return client.put<Attachment>(`/media/${id}`, { description: media[id] })
})).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,104 @@
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) => {
return {
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) => {
return {
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

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