diff --git a/src/renderer/components/TimelineSpace/Contents/Bookmarks.vue b/src/renderer/components/TimelineSpace/Contents/Bookmarks.vue index f50307e5..5f66f3c1 100644 --- a/src/renderer/components/TimelineSpace/Contents/Bookmarks.vue +++ b/src/renderer/components/TimelineSpace/Contents/Bookmarks.vue @@ -30,10 +30,9 @@ import { useStore } from '@/store' import { useI18next } from 'vue3-i18next' import { useRoute } from 'vue-router' import { ElMessage } from 'element-plus' -import { Entity } from 'megalodon' -import useReloadable from '@/components/utils/reloadable' +import parse from 'parse-link-header' +import generator, { Entity, MegalodonInterface } from 'megalodon' import Toot from '@/components/organisms/Toot.vue' -import { ACTION_TYPES, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Bookmarks' import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace' import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu' import { LocalAccount } from '~/src/types/localAccount' @@ -44,15 +43,14 @@ export default defineComponent({ name: 'bookmarks', components: { Toot }, setup() { - const space = 'TimelineSpace/Contents/Bookmarks' const store = useStore() const route = useRoute() const i18n = useI18next() - const { reloadable } = useReloadable(store, route, i18n) const focusedId = ref(null) const heading = ref(true) const scroller = ref() + const loading = ref(false) const lazyLoading = ref(false) const { j, k, Ctrl_r } = useMagicKeys() @@ -63,12 +61,15 @@ export default defineComponent({ account: null, server: null }) + const client = ref(null) - const bookmarks = computed(() => store.state.TimelineSpace.Contents.Bookmarks.bookmarks) + const bookmarks = ref>([]) + const nextMaxId = ref(null) const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload) const modalOpened = computed(() => store.getters[`TimelineSpace/Modals/modalOpened`]) const currentFocusedIndex = computed(() => bookmarks.value.findIndex(toot => focusedId.value === toot.uri)) const shortcutEnabled = computed(() => !modalOpened.value) + const userAgent = computed(() => store.state.App.userAgent) onMounted(async () => { const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value) @@ -76,18 +77,27 @@ export default defineComponent({ account.server = s document.getElementById('scroller')?.addEventListener('scroll', onScroll) - store.commit(`TimelineSpace/Contents/${TIMELINE_MUTATION.CHANGE_LOADING}`, true) - store - .dispatch(`${space}/${ACTION_TYPES.FETCH_BOOKMARKS}`, account) - .catch(() => { - ElMessage({ - message: i18n.t('message.bookmark_fetch_error'), - type: 'error' - }) - }) - .finally(() => { - store.commit(`TimelineSpace/Contents/${TIMELINE_MUTATION.CHANGE_LOADING}`, false) + + client.value = generator(s.sns, s.baseURL, a.accessToken, userAgent.value) + loading.value = true + try { + const res = await client.value.getBookmarks({ limit: 20 }) + bookmarks.value = res.data + const link = parse(res.headers.link) + if (link !== null && link.next) { + nextMaxId.value = link.next.max_id + } else { + nextMaxId.value = null + } + } catch (err) { + console.error(err) + ElMessage({ + message: i18n.t('message.bookmark_fetch_error'), + type: 'error' }) + } finally { + loading.value = false + } }) watch(startReload, (newVal, oldVal) => { if (!oldVal && newVal) { @@ -115,12 +125,23 @@ export default defineComponent({ if ( (event.target as HTMLElement)!.clientHeight + (event.target as HTMLElement)!.scrollTop >= document.getElementById('scroller')!.scrollHeight - 10 && - !lazyLoading.value + !lazyLoading.value && + nextMaxId.value ) { lazyLoading.value = true - store - .dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_BOOKMARKS}`, account) - .catch(() => { + client.value + ?.getBookmarks({ limit: 20, max_id: nextMaxId.value }) + .then(res => { + bookmarks.value = [...bookmarks.value, ...res.data] + const link = parse(res.headers.link) + if (link !== null && link.next) { + nextMaxId.value = link.next.max_id + } else { + nextMaxId.value = null + } + }) + .catch(err => { + console.error(err) ElMessage({ message: i18n.t('message.bookmark_fetch_error'), type: 'error' @@ -130,7 +151,7 @@ export default defineComponent({ lazyLoading.value = false }) } - // for upper + if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) { heading.value = false } else if ((event.target as HTMLElement)!.scrollTop <= 10 && !heading.value) { @@ -139,27 +160,40 @@ export default defineComponent({ } const reload = async () => { store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true) + if (!client.value) return try { - await reloadable() - await store.dispatch(`${space}/${ACTION_TYPES.FETCH_BOOKMARKS}`, account).catch(() => { - ElMessage({ - message: i18n.t('message.bookmark_fetch_error'), - type: 'error' - }) - }) + const res = await client.value.getBookmarks({ limit: 20 }) + bookmarks.value = res.data + const link = parse(res.headers.link) + if (link !== null && link.next) { + nextMaxId.value = link.next.max_id + } else { + nextMaxId.value = null + } } finally { store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, false) } } const updateToot = (message: Entity.Status) => { - store.commit(`${space}/${MUTATION_TYPES.UPDATE_TOOT}`, message) + bookmarks.value = bookmarks.value.map(status => { + if (status.id === message.id) { + return message + } else if (status.reblog && status.reblog.id === message.id) { + return Object.assign(status, { + reblog: message + }) + } + return status + }) } - const deleteToot = (message: Entity.Status) => { - store.commit(`${space}/${MUTATION_TYPES.DELETE_TOOT}`, message) - } - const upper = () => { - scroller.value.scrollToItem(0) - focusedId.value = null + const deleteToot = (id: string) => { + bookmarks.value = bookmarks.value.filter(status => { + if (status.reblog !== null && status.reblog.id === id) { + return false + } else { + return status.id !== id + } + }) } const focusNext = () => { if (currentFocusedIndex.value === -1) { @@ -188,7 +222,6 @@ export default defineComponent({ deleteToot, focusToot, heading, - upper, account } } diff --git a/src/renderer/components/TimelineSpace/Contents/DirectMessages.vue b/src/renderer/components/TimelineSpace/Contents/DirectMessages.vue index 84465c8f..ddaf6e18 100644 --- a/src/renderer/components/TimelineSpace/Contents/DirectMessages.vue +++ b/src/renderer/components/TimelineSpace/Contents/DirectMessages.vue @@ -137,10 +137,6 @@ export default defineComponent({ store.commit(`${space}/${MUTATION_TYPES.DELETE_TOOT}`, { statusId: id, accountId: account.account.id }) } } - const upper = () => { - scroller.value.scrollToItem(0) - focusedId.value = null - } const focusNext = () => { if (currentFocusedIndex.value === -1) { focusedId.value = timeline.value[0].uri + timeline.value[0].id @@ -168,7 +164,6 @@ export default defineComponent({ deleteToot, focusToot, heading, - upper, account } } diff --git a/src/renderer/components/TimelineSpace/Contents/Local.vue b/src/renderer/components/TimelineSpace/Contents/Local.vue index 020a37b1..dbfc6acf 100644 --- a/src/renderer/components/TimelineSpace/Contents/Local.vue +++ b/src/renderer/components/TimelineSpace/Contents/Local.vue @@ -11,9 +11,9 @@ :filters="[]" :account="account.account" :server="account.server" - v-on:update="updateToot" - v-on:delete="deleteToot" - @selectToot="focusToot(item)" + @update="updateToot" + @delete="deleteToot" + @select-toot="focusToot(item)" > @@ -145,10 +145,6 @@ export default defineComponent({ store.commit(`${space}/${MUTATION_TYPES.DELETE_TOOT}`, { statusId: message.id, accountId: account.account.id }) } } - const upper = () => { - scroller.value.scrollToItem(0) - focusedId.value = null - } const focusNext = () => { if (currentFocusedIndex.value === -1) { focusedId.value = timeline.value[0].uri + timeline.value[0].id @@ -175,8 +171,6 @@ export default defineComponent({ updateToot, deleteToot, focusToot, - heading, - upper, account } } diff --git a/src/renderer/components/TimelineSpace/Contents/Notifications.vue b/src/renderer/components/TimelineSpace/Contents/Notifications.vue index 5fd99f25..85feb71a 100644 --- a/src/renderer/components/TimelineSpace/Contents/Notifications.vue +++ b/src/renderer/components/TimelineSpace/Contents/Notifications.vue @@ -17,8 +17,8 @@ :filters="filters" :account="account.account" :server="account.server" - v-on:update="updateToot" - @selectNotification="focusNotification(item)" + @update="updateToot" + @select-notification="focusNotification(item)" > @@ -180,10 +180,7 @@ export default defineComponent({ }, 500) }) } - const upper = () => { - scroller.value.scrollToItem(0) - focusedId.value = null - } + const focusNext = () => { if (currentFocusedIndex.value === -1) { focusedId.value = notifications.value[0].id @@ -213,9 +210,8 @@ export default defineComponent({ focusNext, focusPrev, focusNotification, - heading, - upper, - account + account, + scroller } } }) diff --git a/src/renderer/components/TimelineSpace/Contents/Public.vue b/src/renderer/components/TimelineSpace/Contents/Public.vue index f4a9b461..6185bafc 100644 --- a/src/renderer/components/TimelineSpace/Contents/Public.vue +++ b/src/renderer/components/TimelineSpace/Contents/Public.vue @@ -208,7 +208,6 @@ export default defineComponent({ updateToot, deleteToot, focusToot, - heading, account, backgroundColor } diff --git a/src/renderer/components/utils/reloadable.ts b/src/renderer/components/utils/reloadable.ts deleted file mode 100644 index 54f122c8..00000000 --- a/src/renderer/components/utils/reloadable.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Store } from 'vuex' -import { RootState } from '@/store' -import { RouteLocationNormalizedLoaded } from 'vue-router' -import { i18n } from 'i18next' -import { ElMessage } from 'element-plus' -import { ACTION_TYPES } from '@/store/TimelineSpace' - -export default function useReloadable(store: Store, route: RouteLocationNormalizedLoaded, i18next: i18n) { - async function reloadable() { - const account = await store.dispatch(`TimelineSpace/${ACTION_TYPES.LOCAL_ACCOUNT}`, route.params.id).catch(err => { - ElMessage({ - message: i18next.t('message.account_load_error'), - type: 'error' - }) - throw err - }) - return account - } - - return { - reloadable - } -} diff --git a/src/renderer/store/TimelineSpace/Contents.ts b/src/renderer/store/TimelineSpace/Contents.ts index b6a1d649..45ecdf11 100644 --- a/src/renderer/store/TimelineSpace/Contents.ts +++ b/src/renderer/store/TimelineSpace/Contents.ts @@ -1,6 +1,5 @@ import Home, { HomeState } from './Contents/Home' import Notifications, { NotificationsState } from './Contents/Notifications' -import Bookmarks, { BookmarksState } from './Contents/Bookmarks' import Local, { LocalState } from './Contents/Local' import Search, { SearchModuleState } from './Contents/Search' import Lists, { ListsModuleState } from './Contents/Lists' @@ -17,7 +16,6 @@ type ContentsModule = { Home: HomeState Notifications: NotificationsState DirectMessages: DirectMessagesState - Bookmarks: BookmarksState Local: LocalState Search: SearchModuleState Hashtag: HashtagModuleState @@ -56,7 +54,6 @@ const Contents: Module = { modules: { Home, Notifications, - Bookmarks, Local, DirectMessages, Search, diff --git a/src/renderer/store/TimelineSpace/Contents/Bookmarks.ts b/src/renderer/store/TimelineSpace/Contents/Bookmarks.ts deleted file mode 100644 index a5a00c6c..00000000 --- a/src/renderer/store/TimelineSpace/Contents/Bookmarks.ts +++ /dev/null @@ -1,123 +0,0 @@ -import generator, { Entity } from 'megalodon' -import parse from 'parse-link-header' -import { Module, MutationTree, ActionTree } from 'vuex' -import { RootState } from '@/store' -import { LocalAccount } from '~src/types/localAccount' -import { LocalServer } from '~src/types/localServer' - -export type BookmarksState = { - bookmarks: Array - maxId: string | null -} - -const state = (): BookmarksState => ({ - bookmarks: [], - maxId: null -}) - -export const MUTATION_TYPES = { - UPDATE_BOOKMARKS: 'updateBookmarks', - INSERT_BOOKMARKS: 'insertBookmarks', - UPDATE_TOOT: 'updateToot', - DELETE_TOOT: 'deleteToot', - CHANGE_MAX_ID: 'changeMaxId' -} - -const mutations: MutationTree = { - [MUTATION_TYPES.UPDATE_BOOKMARKS]: (state, bookmarks: Array) => { - state.bookmarks = bookmarks - }, - [MUTATION_TYPES.INSERT_BOOKMARKS]: (state, bookmarks: Array) => { - state.bookmarks = state.bookmarks.concat(bookmarks) - }, - [MUTATION_TYPES.UPDATE_TOOT]: (state, message: Entity.Status) => { - state.bookmarks = state.bookmarks.map(toot => { - if (toot.id === message.id) { - return message - } else if (toot.reblog !== null && toot.reblog.id === message.id) { - // When user reblog/favourite a reblogged toot, target message is a original toot. - // So, a message which is received now is original toot. - const reblog = { - reblog: message - } - return Object.assign(toot, reblog) - } else { - return toot - } - }) - }, - [MUTATION_TYPES.DELETE_TOOT]: (state, message: Entity.Status) => { - state.bookmarks = state.bookmarks.filter(toot => { - if (toot.reblog !== null && toot.reblog.id === message.id) { - return false - } else { - return toot.id !== message.id - } - }) - }, - [MUTATION_TYPES.CHANGE_MAX_ID]: (state, id: string | null) => { - state.maxId = id - } -} - -export const ACTION_TYPES = { - FETCH_BOOKMARKS: 'fetchBookmarks', - LAZY_FETCH_BOOKMARKS: 'lazyFetchBookmarks' -} - -const actions: ActionTree = { - [ACTION_TYPES.FETCH_BOOKMARKS]: async ( - { commit, rootState }, - req: { account: LocalAccount; server: LocalServer } - ): Promise> => { - const client = generator(req.server.sns, req.server.baseURL, req.account.accessToken, rootState.App.userAgent) - const res = await client.getBookmarks({ limit: 20 }) - commit(MUTATION_TYPES.UPDATE_BOOKMARKS, res.data) - // Parse link header - try { - const link = parse(res.headers.link) - if (link !== null && link.next) { - commit(MUTATION_TYPES.CHANGE_MAX_ID, link.next.max_id) - } else { - commit(MUTATION_TYPES.CHANGE_MAX_ID, null) - } - } catch (err) { - commit(MUTATION_TYPES.CHANGE_MAX_ID, null) - console.error(err) - } - return res.data - }, - [ACTION_TYPES.LAZY_FETCH_BOOKMARKS]: async ( - { state, commit, rootState }, - req: { account: LocalAccount; server: LocalServer } - ): Promise | null> => { - if (!state.maxId) { - return Promise.resolve(null) - } - const client = generator(req.server.sns, req.server.baseURL, req.account.accessToken, rootState.App.userAgent) - const res = await client.getFavourites({ max_id: state.maxId, limit: 20 }) - commit(MUTATION_TYPES.INSERT_BOOKMARKS, res.data) - // Parse link header - try { - const link = parse(res.headers.link) - if (link !== null && link.next) { - commit(MUTATION_TYPES.CHANGE_MAX_ID, link.next.max_id) - } else { - commit(MUTATION_TYPES.CHANGE_MAX_ID, null) - } - } catch (err) { - commit(MUTATION_TYPES.CHANGE_MAX_ID, null) - console.error(err) - } - return res.data - } -} - -const Bookmark: Module = { - namespaced: true, - state: state, - mutations: mutations, - actions: actions -} - -export default Bookmark