import emojidata from 'unicode-emoji-json/data-by-emoji.json' import generator, { MegalodonInterface } from 'megalodon' import { Module, MutationTree, ActionTree, GetterTree } from 'vuex' import { RootState } from '@/store/index' import { LocalTag } from '~/src/types/localTag' import { InsertAccountCache } from '~/src/types/insertAccountCache' import { CachedAccount } from '~/src/types/cachedAccount' import { MyWindow } from '~/src/types/global' const win = (window as any) as MyWindow type Suggest = { name: string image?: string | null code?: string | null } type SuggestAccount = Suggest type SuggestHashtag = Suggest type SuggestEmoji = Suggest export type StatusState = { filteredSuggestion: Array filteredAccounts: Array filteredHashtags: Array filteredEmojis: Array openSuggest: boolean startIndex: number | null matchWord: string | null client: MegalodonInterface | null } const state = (): StatusState => ({ filteredSuggestion: [], filteredAccounts: [], filteredHashtags: [], filteredEmojis: [], openSuggest: false, startIndex: null, matchWord: null, client: null }) export const MUTATION_TYPES = { APPEND_FILTERED_ACCOUNTS: 'appendFilteredAccounts', CLEAR_FILTERED_ACCOUNTS: 'clearFilteredAccounts', APPEND_FILTERED_HASHTAGS: 'appendFilteredHashtags', CLEAR_FILTERED_HASHTAGS: 'clearFilteredHashtags', UPDATE_FILTERED_EMOJIS: 'updateFilteredEmojis', CLEAR_FILTERED_EMOJIS: 'clearFilteredEmojis', CHANGE_OPEN_SUGGEST: 'changeOpenSuggest', CHANGE_START_INDEX: 'changeStartIndex', CHANGE_MATCH_WORD: 'changeMatchWord', FILTERED_SUGGESTION_FROM_HASHTAGS: 'filteredSuggestionFromHashtags', FILTERED_SUGGESTION_FROM_ACCOUNTS: 'filteredSuggestionFromAccounts', FILTERED_SUGGESTION_FROM_EMOJIS: 'filteredSuggestionFromEmojis', CLEAR_FILTERED_SUGGESTION: 'clearFilteredSuggestion', SET_CLIENT: 'setClient', CLEAR_CLIENT: 'clearClient' } const mutations: MutationTree = { [MUTATION_TYPES.APPEND_FILTERED_ACCOUNTS]: (state, accounts: Array) => { const suggestion = accounts.map(a => ({ name: `@${a}`, image: null })) const appended = state.filteredAccounts.concat(suggestion) const unique = appended.filter((v1, i1, a1) => { return ( a1.findIndex(v2 => { return v1.name === v2.name }) === i1 ) }) state.filteredAccounts = unique.sort((a, b) => { if (a.name < b.name) return -1 if (a.name > b.name) return 1 return 0 }) }, [MUTATION_TYPES.CLEAR_FILTERED_ACCOUNTS]: state => { state.filteredAccounts = [] }, [MUTATION_TYPES.APPEND_FILTERED_HASHTAGS]: (state, tags: Array) => { const suggestion = tags.map(t => ({ name: `#${t}`, image: null })) const appended = state.filteredHashtags.concat(suggestion) const unique = appended.filter((v1, i1, a1) => { return ( a1.findIndex(v2 => { return v1.name === v2.name }) === i1 ) }) Array.from(new Set(appended)) state.filteredHashtags = unique.sort((a, b) => { if (a.name < b.name) return -1 if (a.name > b.name) return 1 return 0 }) }, [MUTATION_TYPES.CLEAR_FILTERED_HASHTAGS]: state => { state.filteredHashtags = [] }, [MUTATION_TYPES.UPDATE_FILTERED_EMOJIS]: (state, emojis: Array) => { state.filteredEmojis = emojis }, [MUTATION_TYPES.CHANGE_OPEN_SUGGEST]: (state, value: boolean) => { state.openSuggest = value }, [MUTATION_TYPES.CHANGE_START_INDEX]: (state, index: number | null) => { state.startIndex = index }, [MUTATION_TYPES.CHANGE_MATCH_WORD]: (state, word: string | null) => { state.matchWord = word }, [MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS]: state => { state.filteredSuggestion = state.filteredHashtags }, [MUTATION_TYPES.FILTERED_SUGGESTION_FROM_ACCOUNTS]: state => { state.filteredSuggestion = state.filteredAccounts }, [MUTATION_TYPES.FILTERED_SUGGESTION_FROM_EMOJIS]: state => { state.filteredSuggestion = state.filteredEmojis }, [MUTATION_TYPES.CLEAR_FILTERED_SUGGESTION]: state => { state.filteredSuggestion = [] }, [MUTATION_TYPES.SET_CLIENT]: (state, client: MegalodonInterface) => { state.client = client }, [MUTATION_TYPES.CLEAR_CLIENT]: state => { state.client = null } } type WordStart = { word: string start: number } const actions: ActionTree = { suggestAccount: async ({ commit, rootState, dispatch }, wordStart: WordStart) => { dispatch('cancelRequest') commit(MUTATION_TYPES.CLEAR_FILTERED_ACCOUNTS) commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_ACCOUNTS) const { word, start } = wordStart const searchCache = async () => { const target = word.replace('@', '') const accounts: Array = await win.ipcRenderer.invoke('get-cache-accounts', rootState.TimelineSpace.account._id) const matched = accounts.map(account => account.acct).filter(acct => acct.includes(target)) if (matched.length === 0) throw new Error('Empty') commit(MUTATION_TYPES.APPEND_FILTERED_ACCOUNTS, matched) commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true) commit(MUTATION_TYPES.CHANGE_START_INDEX, start) commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word) commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_ACCOUNTS) return matched } const searchAPI = async () => { const client = generator( rootState.TimelineSpace.sns, rootState.TimelineSpace.account.baseURL, rootState.TimelineSpace.account.accessToken, rootState.App.userAgent ) commit(MUTATION_TYPES.SET_CLIENT, client) const res = await client.searchAccount(word) if (res.data.length === 0) throw new Error('Empty') commit( MUTATION_TYPES.APPEND_FILTERED_ACCOUNTS, res.data.map(account => account.acct) ) await win.ipcRenderer.invoke('insert-cache-accounts', { ownerID: rootState.TimelineSpace.account._id!, accts: res.data.map(a => a.acct) } as InsertAccountCache) commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true) commit(MUTATION_TYPES.CHANGE_START_INDEX, start) commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word) commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_ACCOUNTS) return res.data } await Promise.all([searchCache(), searchAPI()]) }, suggestHashtag: async ({ commit, rootState, dispatch }, wordStart: WordStart) => { dispatch('cancelRequest') commit(MUTATION_TYPES.CLEAR_FILTERED_HASHTAGS) commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS) const { word, start } = wordStart const searchCache = async () => { const target = word.replace('#', '') const tags: Array = await win.ipcRenderer.invoke('get-cache-hashtags') const matched = tags.map(tag => tag.tagName).filter(tag => tag.includes(target)) if (matched.length === 0) throw new Error('Empty') commit(MUTATION_TYPES.APPEND_FILTERED_HASHTAGS, matched) commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true) commit(MUTATION_TYPES.CHANGE_START_INDEX, start) commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word) commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS) return matched } const searchAPI = async () => { const client = generator( rootState.TimelineSpace.sns, rootState.TimelineSpace.account.baseURL, rootState.TimelineSpace.account.accessToken, rootState.App.userAgent ) commit(MUTATION_TYPES.SET_CLIENT, client) const res = await client.search(word, 'hashtags') if (res.data.hashtags.length === 0) throw new Error('Empty') commit( MUTATION_TYPES.APPEND_FILTERED_HASHTAGS, res.data.hashtags.map(tag => tag.name) ) await win.ipcRenderer.invoke( 'insert-cache-hashtags', res.data.hashtags.map(tag => tag.name) ) commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true) commit(MUTATION_TYPES.CHANGE_START_INDEX, start) commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word) commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_HASHTAGS) return res.data.hashtags } await Promise.all([searchCache(), searchAPI()]) }, suggestEmoji: ({ commit, rootState }, wordStart: WordStart) => { const { word, start } = wordStart // Find native emojis const filteredEmojiName: Array = Object.keys(emojidata).filter((emoji: string) => `:${emojidata[emoji].name}:`.includes(word)) const filteredNativeEmoji: Array = filteredEmojiName.map((emoji: string) => { return { name: `:${emojidata[emoji].name}:`, code: emoji } }) // Find custom emojis const filteredCustomEmoji: Array = rootState.TimelineSpace.emojis .map(emoji => { return { name: `:${emoji.shortcode}:`, image: emoji.url } }) .filter(emoji => emoji.name.includes(word)) const filtered: Array = filteredNativeEmoji.concat(filteredCustomEmoji) if (filtered.length === 0) throw new Error('Empty') commit( MUTATION_TYPES.UPDATE_FILTERED_EMOJIS, filtered.filter((e, i, array) => { return array.findIndex(ar => e.name === ar.name) === i }) ) commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, true) commit(MUTATION_TYPES.CHANGE_START_INDEX, start) commit(MUTATION_TYPES.CHANGE_MATCH_WORD, word) commit(MUTATION_TYPES.FILTERED_SUGGESTION_FROM_EMOJIS) return filtered }, cancelRequest: ({ state }) => { if (state.client) { state.client.cancel() } }, closeSuggest: ({ commit, dispatch }) => { dispatch('cancelRequest') commit(MUTATION_TYPES.CHANGE_OPEN_SUGGEST, false) commit(MUTATION_TYPES.CHANGE_START_INDEX, null) commit(MUTATION_TYPES.CHANGE_MATCH_WORD, null) commit(MUTATION_TYPES.CLEAR_FILTERED_SUGGESTION) commit(MUTATION_TYPES.CLEAR_FILTERED_ACCOUNTS) commit(MUTATION_TYPES.CLEAR_FILTERED_HASHTAGS) commit(MUTATION_TYPES.CLEAR_CLIENT) } } const getters: GetterTree = { pickerEmojis: (_state, _getters, rootState) => { return rootState.TimelineSpace.emojis .map(emoji => { return { name: `:${emoji.shortcode}:`, image: emoji.url } }) .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 = { namespaced: true, state: state, mutations: mutations, actions: actions, getters: getters } export default Status