[refactor] Home store
This commit is contained in:
parent
797b00d309
commit
49f5be6230
|
@ -1,363 +0,0 @@
|
|||
import { Entity } from 'megalodon'
|
||||
import Home, { HomeState, MUTATION_TYPES } from '@/store/TimelineSpace/Contents/Home'
|
||||
|
||||
const account: Entity.Account = {
|
||||
id: '1',
|
||||
username: 'h3poteto',
|
||||
acct: 'h3poteto@pleroma.io',
|
||||
display_name: 'h3poteto',
|
||||
locked: false,
|
||||
created_at: '2019-03-26T21:30:32',
|
||||
followers_count: 10,
|
||||
following_count: 10,
|
||||
statuses_count: 100,
|
||||
note: 'engineer',
|
||||
url: 'https://pleroma.io',
|
||||
avatar: '',
|
||||
avatar_static: '',
|
||||
header: '',
|
||||
header_static: '',
|
||||
emojis: [],
|
||||
moved: null,
|
||||
fields: null,
|
||||
bot: false
|
||||
}
|
||||
const status1: Entity.Status = {
|
||||
id: '1',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'hoge',
|
||||
plain_content: 'hoge',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const status2: Entity.Status = {
|
||||
id: '2',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: null,
|
||||
content: 'fuga',
|
||||
plain_content: 'fuga',
|
||||
created_at: '2019-03-26T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
|
||||
describe('TimelineSpace/Contents/Home', () => {
|
||||
describe('mutations', () => {
|
||||
let state: HomeState
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
|
||||
describe('changeLazyLoading', () => {
|
||||
it('should be change', () => {
|
||||
Home.mutations![MUTATION_TYPES.CHANGE_LAZY_LOADING](state, true)
|
||||
expect(state.lazyLoading).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('changeHeading', () => {
|
||||
it('should be change', () => {
|
||||
Home.mutations![MUTATION_TYPES.CHANGE_HEADING](state, false)
|
||||
expect(state.heading).toEqual(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('appendTimeline', () => {
|
||||
describe('heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should update timeline', () => {
|
||||
Home.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, status2)
|
||||
expect(state.timeline).toEqual([status2, status1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status2, status1],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not update timeline', () => {
|
||||
Home.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, status2)
|
||||
expect(state.timeline).toEqual([status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('not heading', () => {
|
||||
describe('normal', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [status1],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should update timeline', () => {
|
||||
Home.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, status2)
|
||||
expect(state.timeline).toEqual([status1])
|
||||
expect(state.unreads).toEqual([status2])
|
||||
})
|
||||
})
|
||||
describe('duplicated status', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: false,
|
||||
timeline: [status2, status1],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should not update timeline', () => {
|
||||
Home.mutations![MUTATION_TYPES.APPEND_TIMELINE](state, status2)
|
||||
expect(state.timeline).toEqual([status2, status1])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('insertTimeline', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be inserted', () => {
|
||||
Home.mutations![MUTATION_TYPES.INSERT_TIMELINE](state, [status2])
|
||||
expect(state.timeline).toEqual([status1, status2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('updateToot', () => {
|
||||
describe('message is not reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1, status2],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
const favouritedStatus: Entity.Status = Object.assign(status1, {
|
||||
favourited: true
|
||||
})
|
||||
it('should be updated', () => {
|
||||
Home.mutations![MUTATION_TYPES.UPDATE_TOOT](state, favouritedStatus)
|
||||
expect(state.timeline).toEqual([favouritedStatus, status2])
|
||||
})
|
||||
})
|
||||
describe('message is reblogged', () => {
|
||||
const rebloggedStatus: Entity.Status = {
|
||||
id: '3',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: status1,
|
||||
content: '',
|
||||
plain_content: null,
|
||||
created_at: '2019-03-31T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
const favouritedStatus: Entity.Status = Object.assign(status1, {
|
||||
favourited: true
|
||||
})
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [rebloggedStatus, status2],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be updated', () => {
|
||||
Home.mutations![MUTATION_TYPES.UPDATE_TOOT](state, favouritedStatus)
|
||||
expect((state.timeline[0] as Entity.Status).reblog).not.toBeNull()
|
||||
expect((state.timeline[0] as Entity.Status).reblog!.favourited).toEqual(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteToot', () => {
|
||||
describe('message is not reblogged', () => {
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [status1, status2],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Home.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
|
||||
describe('message is reblogged', () => {
|
||||
beforeEach(() => {
|
||||
const rebloggedStatus: Entity.Status = {
|
||||
id: '3',
|
||||
uri: 'http://example.com',
|
||||
url: 'http://example.com',
|
||||
account: account,
|
||||
in_reply_to_id: null,
|
||||
in_reply_to_account_id: null,
|
||||
reblog: status1,
|
||||
content: '',
|
||||
plain_content: null,
|
||||
created_at: '2019-03-31T21:40:32',
|
||||
emojis: [],
|
||||
replies_count: 0,
|
||||
reblogs_count: 0,
|
||||
favourites_count: 0,
|
||||
reblogged: null,
|
||||
favourited: null,
|
||||
muted: null,
|
||||
sensitive: false,
|
||||
spoiler_text: '',
|
||||
visibility: 'public',
|
||||
media_attachments: [],
|
||||
mentions: [],
|
||||
tags: [],
|
||||
card: null,
|
||||
poll: null,
|
||||
application: {
|
||||
name: 'Web'
|
||||
} as Entity.Application,
|
||||
language: null,
|
||||
pinned: null,
|
||||
emoji_reactions: [],
|
||||
bookmarked: false,
|
||||
quote: false
|
||||
}
|
||||
state = {
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [rebloggedStatus, status2],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
}
|
||||
})
|
||||
it('should be deleted', () => {
|
||||
Home.mutations![MUTATION_TYPES.DELETE_TOOT](state, status1.id)
|
||||
expect(state.timeline).toEqual([status2])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div id="home">
|
||||
<div class="unread">{{ unreads.length > 0 ? unreads.length : '' }}</div>
|
||||
<DynamicScroller :items="filteredTimeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
|
||||
<template v-slot="{ item, index, active }">
|
||||
<template v-if="item.id === 'loading-card'">
|
||||
|
@ -11,10 +10,13 @@
|
|||
<template v-else>
|
||||
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
|
||||
<toot
|
||||
v-if="account.account && account.server"
|
||||
:message="item"
|
||||
:focused="item.uri + item.id === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
:filters="filters"
|
||||
:account="account.account"
|
||||
:server="account.server"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusRight="focusSidebar"
|
||||
|
@ -35,7 +37,7 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, computed, onMounted, onBeforeUpdate, watch, onUnmounted } from 'vue'
|
||||
import { defineComponent, ref, computed, onMounted, onBeforeUpdate, watch, onUnmounted, reactive } from 'vue'
|
||||
import { logicAnd } from '@vueuse/math'
|
||||
import { useMagicKeys, whenever } from '@vueuse/core'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
@ -51,6 +53,9 @@ import { MUTATION_TYPES as SIDE_MENU_MUTATION } from '@/store/TimelineSpace/Side
|
|||
import { MUTATION_TYPES as TIMELINE_MUTATION } from '@/store/TimelineSpace'
|
||||
import { MUTATION_TYPES as HEADER_MUTATION } from '@/store/TimelineSpace/HeaderMenu'
|
||||
import useReloadable from '@/components/utils/reloadable'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'home',
|
||||
|
@ -63,16 +68,24 @@ export default defineComponent({
|
|||
const { reloadable } = useReloadable(store, route, i18n)
|
||||
const { j, k, Ctrl_r } = useMagicKeys()
|
||||
|
||||
const win = (window as any) as MyWindow
|
||||
|
||||
const id = computed(() => parseInt(route.params.id as string))
|
||||
|
||||
const focusedId = ref<string | null>(null)
|
||||
const loadingMore = ref(false)
|
||||
const scroller = ref<any>()
|
||||
const lazyLoading = ref(false)
|
||||
const heading = ref(true)
|
||||
const showReblogs = ref(true)
|
||||
const showReplies = ref(true)
|
||||
const account = reactive<{ account: LocalAccount | null; server: LocalServer | null }>({
|
||||
account: null,
|
||||
server: null
|
||||
})
|
||||
|
||||
const timeline = computed(() => store.state.TimelineSpace.Contents.Home.timeline[id.value])
|
||||
|
||||
const timeline = computed(() => store.state.TimelineSpace.Contents.Home.timeline)
|
||||
const unreads = computed(() => store.state.TimelineSpace.Contents.Home.unreads)
|
||||
const lazyLoading = computed(() => store.state.TimelineSpace.Contents.Home.lazyLoading)
|
||||
const heading = computed(() => store.state.TimelineSpace.Contents.Home.heading)
|
||||
const showReblogs = computed(() => store.state.TimelineSpace.Contents.Home.showReblogs)
|
||||
const showReplies = computed(() => store.state.TimelineSpace.Contents.Home.showReplies)
|
||||
const openSideBar = computed(() => store.state.TimelineSpace.Contents.SideBar.openSideBar)
|
||||
const startReload = computed(() => store.state.TimelineSpace.HeaderMenu.reload)
|
||||
const modalOpened = computed<boolean>(() => store.getters[`TimelineSpace/Modals/modalOpened`])
|
||||
|
@ -80,6 +93,9 @@ export default defineComponent({
|
|||
const currentFocusedIndex = computed(() => timeline.value.findIndex(toot => focusedId.value === toot.uri + toot.id))
|
||||
const shortcutEnabled = computed(() => !modalOpened.value)
|
||||
const filteredTimeline = computed(() => {
|
||||
if (!timeline.value) {
|
||||
return []
|
||||
}
|
||||
return timeline.value.filter(toot => {
|
||||
if ('url' in toot) {
|
||||
if (toot.in_reply_to_id) {
|
||||
|
@ -95,13 +111,13 @@ export default defineComponent({
|
|||
})
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
const [a, s]: [LocalAccount, LocalServer] = await win.ipcRenderer.invoke('get-local-account', id.value)
|
||||
account.account = a
|
||||
account.server = s
|
||||
|
||||
store.commit(`TimelineSpace/SideMenu/${SIDE_MENU_MUTATION.CHANGE_UNREAD_HOME_TIMELINE}`, false)
|
||||
document.getElementById('scroller')?.addEventListener('scroll', onScroll)
|
||||
|
||||
if (heading.value && timeline.value.length > 0) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`)
|
||||
}
|
||||
})
|
||||
onBeforeUpdate(() => {
|
||||
if (store.state.TimelineSpace.SideMenu.unreadHomeTimeline && heading.value) {
|
||||
|
@ -109,8 +125,6 @@ export default defineComponent({
|
|||
}
|
||||
})
|
||||
onUnmounted(() => {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.ARCHIVE_TIMELINE}`)
|
||||
const el = document.getElementById('scroller')
|
||||
if (el !== undefined && el !== null) {
|
||||
el.removeEventListener('scroll', onScroll)
|
||||
|
@ -127,17 +141,17 @@ export default defineComponent({
|
|||
watch(
|
||||
timeline,
|
||||
(newState, _oldState) => {
|
||||
if (heading.value && newState.length > 0) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`)
|
||||
if (heading.value && newState.length > 0 && account.account && account.server) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.SAVE_MARKER}`, account)
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
watch(focusedId, (newVal, _oldVal) => {
|
||||
if (newVal && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if (newVal === null && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
heading.value = true
|
||||
}
|
||||
})
|
||||
whenever(logicAnd(j, shortcutEnabled), () => {
|
||||
|
@ -161,19 +175,28 @@ export default defineComponent({
|
|||
document.getElementById('scroller')!.scrollHeight - 10 &&
|
||||
!lazyLoading.value
|
||||
) {
|
||||
store.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, timeline.value[timeline.value.length - 1]).catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
lazyLoading.value = true
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.LAZY_FETCH_TIMELINE}`, {
|
||||
lastStatus: timeline.value[timeline.value.length - 1],
|
||||
account: account.account,
|
||||
server: account.server
|
||||
})
|
||||
.catch(() => {
|
||||
ElMessage({
|
||||
message: i18n.t('message.timeline_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
lazyLoading.value = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if ((event.target as HTMLElement)!.scrollTop > 10 && heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, false)
|
||||
heading.value = false
|
||||
} else if ((event.target as HTMLElement)!.scrollTop <= 5 && !heading.value) {
|
||||
store.commit(`${space}/${MUTATION_TYPES.CHANGE_HEADING}`, true)
|
||||
store.commit(`${space}/${MUTATION_TYPES.MERGE_UNREADS}`)
|
||||
heading.value = true
|
||||
}
|
||||
}
|
||||
const updateToot = (message: Entity.Status) => {
|
||||
|
@ -184,11 +207,13 @@ export default defineComponent({
|
|||
}
|
||||
const fetchTimelineSince = (since_id: string) => {
|
||||
loadingMore.value = true
|
||||
store.dispatch(`${space}/${ACTION_TYPES.FETCH_TIMELINE_SINCE}`, since_id).finally(() => {
|
||||
setTimeout(() => {
|
||||
loadingMore.value = false
|
||||
}, 500)
|
||||
})
|
||||
store
|
||||
.dispatch(`${space}/${ACTION_TYPES.FETCH_TIMELINE_SINCE}`, { sinceId: since_id, account: account.account, server: account.server })
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
loadingMore.value = false
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
const reload = async () => {
|
||||
store.commit(`TimelineSpace/${TIMELINE_MUTATION.CHANGE_LOADING}`, true)
|
||||
|
@ -240,7 +265,7 @@ export default defineComponent({
|
|||
openSideBar,
|
||||
heading,
|
||||
upper,
|
||||
unreads
|
||||
account
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -261,6 +261,8 @@ import { ACTION_TYPES as REPORT_ACTION } from '@/store/TimelineSpace/Modals/Repo
|
|||
import { ACTION_TYPES as MUTE_ACTION } from '@/store/TimelineSpace/Modals/MuteConfirm'
|
||||
import { ACTION_TYPES as VIEWER_ACTION } from '@/store/TimelineSpace/Modals/ImageViewer'
|
||||
import { ACTION_TYPES } from '@/store/organisms/Toot'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
|
||||
const defaultEmojiIndex = new EmojiIndex(data)
|
||||
|
||||
|
@ -297,6 +299,14 @@ export default defineComponent({
|
|||
detailed: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
account: {
|
||||
type: Object as PropType<LocalAccount>,
|
||||
required: true
|
||||
},
|
||||
server: {
|
||||
type: Object as PropType<LocalServer>,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
emits: ['selectToot', 'focusRight', 'focusLeft', 'update', 'delete', 'sizeChanged'],
|
||||
|
@ -306,7 +316,7 @@ export default defineComponent({
|
|||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const i18n = useI18next()
|
||||
const { focused, overlaid, message, filters } = toRefs(props)
|
||||
const { focused, overlaid, message, filters, account, server } = toRefs(props)
|
||||
const { l, h, r, b, f, o, p, i, x } = useMagicKeys()
|
||||
|
||||
const statusRef = ref<any>(null)
|
||||
|
@ -319,8 +329,6 @@ export default defineComponent({
|
|||
const displayNameStyle = computed(() => store.state.App.displayNameStyle)
|
||||
const timeFormat = computed(() => store.state.App.timeFormat)
|
||||
const language = computed(() => store.state.App.language)
|
||||
const server = computed(() => store.state.TimelineSpace.server)
|
||||
const account = computed(() => store.state.TimelineSpace.account)
|
||||
const shortcutEnabled = computed(() => focused.value && !overlaid.value)
|
||||
const originalMessage = computed(() => {
|
||||
if (message.value.reblog && !message.value.quote) {
|
||||
|
@ -350,7 +358,7 @@ export default defineComponent({
|
|||
return null
|
||||
})
|
||||
const isMyMessage = computed(() => {
|
||||
return account.value!.accountId === originalMessage.value.account.id
|
||||
return account.value.accountId === originalMessage.value.account.id
|
||||
})
|
||||
const application = computed(() => {
|
||||
const msg = originalMessage.value
|
||||
|
@ -384,7 +392,7 @@ export default defineComponent({
|
|||
return originalMessage.value.visibility === 'direct'
|
||||
})
|
||||
const quoteSupported = computed(() => {
|
||||
return QuoteSupported(server.value!.sns, server.value!.domain)
|
||||
return QuoteSupported(server.value.sns, server.value.domain)
|
||||
})
|
||||
|
||||
whenever(logicAnd(l, shortcutEnabled), () => {
|
||||
|
|
|
@ -45,6 +45,8 @@ export const ACTION_TYPES = {
|
|||
REMOVE_SHORTCUT_EVENTS: 'removeShortcutEvents',
|
||||
LOAD_HIDE: 'loadHide',
|
||||
SWITCH_HIDE: 'switchHide',
|
||||
LOAD_TIMELINES: 'loadTimelines',
|
||||
BIND_STREAMINGS: 'bindStreamings',
|
||||
BIND_NOTIFICATION: 'bindNotification'
|
||||
}
|
||||
|
||||
|
@ -59,6 +61,8 @@ const actions: ActionTree<GlobalHeaderState, RootState> = {
|
|||
console.error(err)
|
||||
}
|
||||
const accounts = await dispatch(ACTION_TYPES.LIST_ACCOUNTS)
|
||||
await dispatch(ACTION_TYPES.LOAD_TIMELINES, accounts)
|
||||
await dispatch(ACTION_TYPES.BIND_STREAMINGS, accounts)
|
||||
// Block to root path when user use browser-back, like mouse button.
|
||||
// Because any contents are not rendered when browser back to / from home.
|
||||
router.beforeEach((to, from, next) => {
|
||||
|
@ -112,6 +116,19 @@ const actions: ActionTree<GlobalHeaderState, RootState> = {
|
|||
// We have to wait until change el-menu-item
|
||||
setTimeout(() => router.push(`/${id}/notifications`), 500)
|
||||
})
|
||||
},
|
||||
[ACTION_TYPES.LOAD_TIMELINES]: async ({ dispatch }, req: Array<[LocalAccount, LocalServer]>) => {
|
||||
req.forEach(async ([account, server]) => {
|
||||
await dispatch('TimelineSpace/Contents/Home/fetchTimeline', { account, server }, { root: true })
|
||||
})
|
||||
},
|
||||
[ACTION_TYPES.BIND_STREAMINGS]: async ({ commit }, req: Array<[LocalAccount, LocalServer]>) => {
|
||||
req.forEach(async ([account, _server]) => {
|
||||
win.ipcRenderer.removeAllListeners(`update-user-streamings-${account.id}`)
|
||||
win.ipcRenderer.on(`update-user-streamings-${account.id}`, (_, update: Entity.Status) => {
|
||||
commit('TimelineSpace/Contents/Home/appendTimeline', { status: update, accountId: account.id }, { root: true })
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { RootState } from '@/store'
|
|||
import { AccountLoadError } from '@/errors/load'
|
||||
import { TimelineFetchError } from '@/errors/fetch'
|
||||
import { MyWindow } from '~/src/types/global'
|
||||
import { LocalServer } from '~src/types/localServer'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { Setting } from '~/src/types/setting'
|
||||
import { DefaultSetting } from '~/src/constants/initializer/setting'
|
||||
|
||||
|
@ -199,11 +199,6 @@ const actions: ActionTree<TimelineSpaceState, RootState> = {
|
|||
return true
|
||||
},
|
||||
[ACTION_TYPES.FETCH_CONTENTS_TIMELINES]: async ({ dispatch }) => {
|
||||
dispatch('TimelineSpace/Contents/changeLoading', true, { root: true })
|
||||
await dispatch('TimelineSpace/Contents/Home/fetchTimeline', {}, { root: true }).finally(() => {
|
||||
dispatch('TimelineSpace/Contents/changeLoading', false, { root: true })
|
||||
})
|
||||
|
||||
await dispatch('TimelineSpace/Contents/Notifications/fetchNotifications', {}, { root: true })
|
||||
await dispatch('TimelineSpace/Contents/Mentions/fetchMentions', {}, { root: true })
|
||||
await dispatch('TimelineSpace/Contents/DirectMessages/fetchTimeline', {}, { root: true })
|
||||
|
@ -211,7 +206,6 @@ const actions: ActionTree<TimelineSpaceState, RootState> = {
|
|||
await dispatch('TimelineSpace/Contents/Public/fetchPublicTimeline', {}, { root: true })
|
||||
},
|
||||
[ACTION_TYPES.CLEAR_CONTENTS_TIMELINES]: ({ commit }) => {
|
||||
commit('TimelineSpace/Contents/Home/clearTimeline', {}, { root: true })
|
||||
commit('TimelineSpace/Contents/Local/clearTimeline', {}, { root: true })
|
||||
commit('TimelineSpace/Contents/DirectMessages/clearTimeline', {}, { root: true })
|
||||
commit('TimelineSpace/Contents/Notifications/clearNotifications', {}, { root: true })
|
||||
|
@ -232,14 +226,6 @@ const actions: ActionTree<TimelineSpaceState, RootState> = {
|
|||
throw new Error('Account is not set')
|
||||
}
|
||||
|
||||
win.ipcRenderer.on(`update-user-streamings-${state.account!.id}`, (_, update: Entity.Status) => {
|
||||
commit('TimelineSpace/Contents/Home/appendTimeline', update, { root: true })
|
||||
// Sometimes archive old statuses
|
||||
if (rootState.TimelineSpace.Contents.Home.heading && Math.random() > 0.8) {
|
||||
commit('TimelineSpace/Contents/Home/archiveTimeline', null, { root: true })
|
||||
}
|
||||
commit('TimelineSpace/SideMenu/changeUnreadHomeTimeline', true, { root: true })
|
||||
})
|
||||
win.ipcRenderer.on(`notification-user-streamings-${state.account!.id}`, (_, notification: Entity.Notification) => {
|
||||
commit('TimelineSpace/Contents/Notifications/appendNotifications', notification, { root: true })
|
||||
if (rootState.TimelineSpace.Contents.Notifications.heading && Math.random() > 0.8) {
|
||||
|
|
|
@ -2,85 +2,58 @@ import generator, { Entity, FilterContext } from 'megalodon'
|
|||
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex'
|
||||
import { RootState } from '@/store'
|
||||
import { LoadingCard } from '@/types/loading-card'
|
||||
import { LocalServer } from '~/src/types/localServer'
|
||||
import { LocalAccount } from '~/src/types/localAccount'
|
||||
|
||||
export type HomeState = {
|
||||
lazyLoading: boolean
|
||||
heading: boolean
|
||||
showReblogs: boolean
|
||||
showReplies: boolean
|
||||
timeline: Array<Entity.Status | LoadingCard>
|
||||
unreads: Array<Entity.Status>
|
||||
timeline: { [key: number]: Array<Entity.Status | LoadingCard> }
|
||||
}
|
||||
|
||||
const state = (): HomeState => ({
|
||||
lazyLoading: false,
|
||||
heading: true,
|
||||
timeline: [],
|
||||
showReblogs: true,
|
||||
showReplies: true,
|
||||
unreads: []
|
||||
timeline: {}
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
CHANGE_LAZY_LOADING: 'changeLazyLoading',
|
||||
CHANGE_HEADING: 'changeHeading',
|
||||
APPEND_TIMELINE: 'appendTimeline',
|
||||
UPDATE_TIMELINE: 'updateTimeline',
|
||||
REPLACE_TIMELINE: 'replaceTimeline',
|
||||
INSERT_TIMELINE: 'insertTimeline',
|
||||
ARCHIVE_TIMELINE: 'archiveTimeline',
|
||||
CLEAR_TIMELINE: 'clearTimeline',
|
||||
UPDATE_TOOT: 'updateToot',
|
||||
DELETE_TOOT: 'deleteToot',
|
||||
SHOW_REBLOGS: 'showReblogs',
|
||||
SHOW_REPLIES: 'showReplies',
|
||||
APPEND_TIMELINE_AFTER_LOADING_CARD: 'appendTimelineAfterLoadingCard',
|
||||
MERGE_UNREADS: 'mergeUnreads'
|
||||
APPEND_TIMELINE_AFTER_LOADING_CARD: 'appendTimelineAfterLoadingCard'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<HomeState> = {
|
||||
[MUTATION_TYPES.CHANGE_LAZY_LOADING]: (state, value: boolean) => {
|
||||
state.lazyLoading = value
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_HEADING]: (state, value: boolean) => {
|
||||
state.heading = value
|
||||
},
|
||||
[MUTATION_TYPES.APPEND_TIMELINE]: (state, update: Entity.Status) => {
|
||||
// Reject duplicated status in timeline
|
||||
if (!state.timeline.find(item => item.id === update.id) && !state.unreads.find(item => item.id === update.id)) {
|
||||
if (state.heading) {
|
||||
state.timeline = ([update] as Array<Entity.Status | LoadingCard>).concat(state.timeline)
|
||||
} else {
|
||||
state.unreads = [update].concat(state.unreads)
|
||||
}
|
||||
[MUTATION_TYPES.APPEND_TIMELINE]: (state, obj: { status: Entity.Status; accountId: number }) => {
|
||||
if (state.timeline[obj.accountId]) {
|
||||
state.timeline[obj.accountId] = [obj.status, ...state.timeline[obj.accountId]]
|
||||
} else {
|
||||
state.timeline[obj.accountId] = [obj.status]
|
||||
}
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_TIMELINE]: (state, messages: Array<Entity.Status | LoadingCard>) => {
|
||||
state.timeline = messages
|
||||
[MUTATION_TYPES.REPLACE_TIMELINE]: (state, obj: { statuses: Array<Entity.Status | LoadingCard>; accountId: number }) => {
|
||||
state.timeline[obj.accountId] = obj.statuses
|
||||
},
|
||||
[MUTATION_TYPES.INSERT_TIMELINE]: (state, messages: Array<Entity.Status | LoadingCard>) => {
|
||||
state.timeline = state.timeline.concat(messages)
|
||||
[MUTATION_TYPES.INSERT_TIMELINE]: (state, obj: { statuses: Array<Entity.Status | LoadingCard>; accountId: number }) => {
|
||||
if (state.timeline[obj.accountId]) {
|
||||
state.timeline[obj.accountId] = [...state.timeline[obj.accountId], ...obj.statuses]
|
||||
} else {
|
||||
state.timeline[obj.accountId] = obj.statuses
|
||||
}
|
||||
},
|
||||
[MUTATION_TYPES.ARCHIVE_TIMELINE]: state => {
|
||||
state.timeline = state.timeline.slice(0, 20)
|
||||
},
|
||||
[MUTATION_TYPES.CLEAR_TIMELINE]: state => {
|
||||
state.timeline = []
|
||||
state.unreads = []
|
||||
},
|
||||
[MUTATION_TYPES.UPDATE_TOOT]: (state, message: Entity.Status) => {
|
||||
[MUTATION_TYPES.UPDATE_TOOT]: (state, obj: { status: Entity.Status; accountId: number }) => {
|
||||
// Replace target message in homeTimeline and notifications
|
||||
state.timeline = state.timeline.map(status => {
|
||||
state.timeline[obj.accountId] = state.timeline[obj.accountId].map(status => {
|
||||
if (status.id === 'loading-card') {
|
||||
return status
|
||||
}
|
||||
const toot = status as Entity.Status
|
||||
if (toot.id === message.id) {
|
||||
return message
|
||||
} else if (toot.reblog !== null && toot.reblog.id === message.id) {
|
||||
if (toot.id === obj.status.id) {
|
||||
return obj.status
|
||||
} else if (toot.reblog !== null && toot.reblog.id === obj.status.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
|
||||
reblog: obj.status
|
||||
}
|
||||
return Object.assign(toot, reblog)
|
||||
} else {
|
||||
|
@ -88,39 +61,32 @@ const mutations: MutationTree<HomeState> = {
|
|||
}
|
||||
})
|
||||
},
|
||||
[MUTATION_TYPES.DELETE_TOOT]: (state, messageId: string) => {
|
||||
state.timeline = state.timeline.filter(status => {
|
||||
[MUTATION_TYPES.DELETE_TOOT]: (state, obj: { statusId: string; accountId: number }) => {
|
||||
state.timeline[obj.accountId] = state.timeline[obj.accountId].filter(status => {
|
||||
if (status.id === 'loading-card') {
|
||||
return true
|
||||
}
|
||||
const toot = status as Entity.Status
|
||||
if (toot.reblog !== null && toot.reblog.id === messageId) {
|
||||
if (toot.reblog !== null && toot.reblog.id === obj.statusId) {
|
||||
return false
|
||||
} else {
|
||||
return toot.id !== messageId
|
||||
return toot.id !== obj.statusId
|
||||
}
|
||||
})
|
||||
},
|
||||
[MUTATION_TYPES.SHOW_REBLOGS]: (state, visible: boolean) => {
|
||||
state.showReblogs = visible
|
||||
},
|
||||
[MUTATION_TYPES.SHOW_REPLIES]: (state, visible: boolean) => {
|
||||
state.showReplies = visible
|
||||
},
|
||||
[MUTATION_TYPES.APPEND_TIMELINE_AFTER_LOADING_CARD]: (state, timeline: Array<Entity.Status | LoadingCard>) => {
|
||||
const tl = state.timeline.flatMap(status => {
|
||||
[MUTATION_TYPES.APPEND_TIMELINE_AFTER_LOADING_CARD]: (
|
||||
state,
|
||||
obj: { statuses: Array<Entity.Status | LoadingCard>; accountId: number }
|
||||
) => {
|
||||
const tl = state.timeline[obj.accountId].flatMap(status => {
|
||||
if (status.id !== 'loading-card') {
|
||||
return status
|
||||
} else {
|
||||
return timeline
|
||||
return obj.statuses
|
||||
}
|
||||
})
|
||||
// Reject duplicated status in timeline
|
||||
state.timeline = Array.from(new Set(tl))
|
||||
},
|
||||
[MUTATION_TYPES.MERGE_UNREADS]: state => {
|
||||
state.timeline = (state.unreads.slice(0, 80) as Array<Entity.Status | LoadingCard>).concat(state.timeline)
|
||||
state.unreads = []
|
||||
state.timeline[obj.accountId] = Array.from(new Set(tl))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,14 +99,10 @@ export const ACTION_TYPES = {
|
|||
}
|
||||
|
||||
const actions: ActionTree<HomeState, RootState> = {
|
||||
[ACTION_TYPES.FETCH_TIMELINE]: async ({ dispatch, commit, rootState }) => {
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
const marker: Entity.Marker | null = await dispatch('getMarker').catch(err => {
|
||||
// vue
|
||||
[ACTION_TYPES.FETCH_TIMELINE]: async ({ dispatch, commit, rootState }, req: { account: LocalAccount; server: LocalServer }) => {
|
||||
const client = generator(req.server.sns, req.server.baseURL, req.account.accessToken, rootState.App.userAgent)
|
||||
const marker: Entity.Marker | null = await dispatch(ACTION_TYPES.GET_MARKER, req).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
|
||||
|
@ -171,46 +133,30 @@ const actions: ActionTree<HomeState, RootState> = {
|
|||
} else {
|
||||
timeline = timeline.concat(res.data)
|
||||
}
|
||||
commit(MUTATION_TYPES.UPDATE_TIMELINE, timeline)
|
||||
commit(MUTATION_TYPES.REPLACE_TIMELINE, { statuses: timeline, accountId: req.account.id })
|
||||
return res.data
|
||||
} else {
|
||||
const res = await client.getHomeTimeline({ limit: 20 })
|
||||
commit(MUTATION_TYPES.UPDATE_TIMELINE, res.data)
|
||||
commit(MUTATION_TYPES.REPLACE_TIMELINE, { statuses: res.data, accountId: req.account.id })
|
||||
return res.data
|
||||
}
|
||||
},
|
||||
[ACTION_TYPES.LAZY_FETCH_TIMELINE]: async (
|
||||
{ state, commit, rootState },
|
||||
lastStatus: Entity.Status
|
||||
{ commit, rootState },
|
||||
req: { lastStatus: Entity.Status; account: LocalAccount; server: LocalServer }
|
||||
): Promise<Array<Entity.Status> | null> => {
|
||||
if (state.lazyLoading) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
commit(MUTATION_TYPES.CHANGE_LAZY_LOADING, true)
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
return client
|
||||
.getHomeTimeline({ max_id: lastStatus.id, limit: 20 })
|
||||
.then(res => {
|
||||
commit(MUTATION_TYPES.INSERT_TIMELINE, res.data)
|
||||
return res.data
|
||||
})
|
||||
.finally(() => {
|
||||
commit(MUTATION_TYPES.CHANGE_LAZY_LOADING, false)
|
||||
})
|
||||
const client = generator(req.server.sns, req.server.baseURL, req.account.accessToken, rootState.App.userAgent)
|
||||
return client.getHomeTimeline({ max_id: req.lastStatus.id, limit: 20 }).then(res => {
|
||||
commit(MUTATION_TYPES.INSERT_TIMELINE, { statuses: res.data, accountId: req.account.id })
|
||||
return res.data
|
||||
})
|
||||
},
|
||||
[ACTION_TYPES.FETCH_TIMELINE_SINCE]: async ({ state, rootState, commit }, since_id: string): Promise<Array<Entity.Status> | null> => {
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
const cardIndex = state.timeline.findIndex(s => {
|
||||
[ACTION_TYPES.FETCH_TIMELINE_SINCE]: async (
|
||||
{ state, rootState, commit },
|
||||
req: { sinceId: string; account: LocalAccount; server: LocalServer }
|
||||
): Promise<Array<Entity.Status> | null> => {
|
||||
const client = generator(req.server.sns, req.server.baseURL, req.account.accessToken, rootState.App.userAgent)
|
||||
const cardIndex = state.timeline[req.account.id].findIndex(s => {
|
||||
if (s.id === 'loading-card') {
|
||||
return true
|
||||
}
|
||||
|
@ -218,7 +164,7 @@ const actions: ActionTree<HomeState, RootState> = {
|
|||
})
|
||||
let maxID: string | null = null
|
||||
if (cardIndex > 0) {
|
||||
maxID = state.timeline[cardIndex - 1].id
|
||||
maxID = state.timeline[req.account.id][cardIndex - 1].id
|
||||
}
|
||||
// Memo: What happens when we specify both of max_id and min_id?
|
||||
// What is the difference between max_id & since_id and max_id & min_id?
|
||||
|
@ -230,7 +176,7 @@ const actions: ActionTree<HomeState, RootState> = {
|
|||
// Also, we can get statuses which are older than max_id and newer than min_id.
|
||||
// If the number of statuses exceeds the limit, it truncates newer statuses.
|
||||
// That means, the status immediately before max_id is not included in the response.
|
||||
let params = { min_id: since_id, limit: 20 }
|
||||
let params = { min_id: req.sinceId, limit: 20 }
|
||||
if (maxID !== null) {
|
||||
params = Object.assign({}, params, {
|
||||
max_id: maxID
|
||||
|
@ -248,22 +194,17 @@ const actions: ActionTree<HomeState, RootState> = {
|
|||
}
|
||||
let timeline: Array<Entity.Status | LoadingCard> = [card]
|
||||
timeline = timeline.concat(res.data)
|
||||
commit(MUTATION_TYPES.APPEND_TIMELINE_AFTER_LOADING_CARD, timeline)
|
||||
commit(MUTATION_TYPES.APPEND_TIMELINE_AFTER_LOADING_CARD, { statuses: timeline, accountId: req.account.id })
|
||||
} else {
|
||||
commit(MUTATION_TYPES.APPEND_TIMELINE_AFTER_LOADING_CARD, res.data)
|
||||
commit(MUTATION_TYPES.APPEND_TIMELINE_AFTER_LOADING_CARD, { statuses: res.data, accountId: req.account.id })
|
||||
}
|
||||
return res.data
|
||||
},
|
||||
[ACTION_TYPES.GET_MARKER]: async ({ rootState }): Promise<Entity.Marker | null> => {
|
||||
[ACTION_TYPES.GET_MARKER]: async ({ rootState }, req: { account: LocalAccount; server: LocalServer }): Promise<Entity.Marker | null> => {
|
||||
if (!rootState.TimelineSpace.setting.markerHome) {
|
||||
return null
|
||||
}
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
const client = generator(req.server.sns, req.server.baseURL, req.account.accessToken, rootState.App.userAgent)
|
||||
let serverMarker: Entity.Marker | {} = {}
|
||||
try {
|
||||
const res = await client.getMarkers(['home'])
|
||||
|
@ -273,20 +214,15 @@ const actions: ActionTree<HomeState, RootState> = {
|
|||
}
|
||||
return serverMarker
|
||||
},
|
||||
[ACTION_TYPES.SAVE_MARKER]: async ({ state, rootState }) => {
|
||||
const timeline = state.timeline
|
||||
[ACTION_TYPES.SAVE_MARKER]: async ({ state, rootState }, req: { account: LocalAccount; server: LocalServer }) => {
|
||||
const timeline = state.timeline[req.account.id]
|
||||
if (timeline.length === 0 || timeline[0].id === 'loading-card') {
|
||||
return
|
||||
}
|
||||
if (rootState.TimelineSpace.server!.sns === 'misskey') {
|
||||
if (req.server.sns === 'misskey') {
|
||||
return
|
||||
}
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.server!.sns,
|
||||
rootState.TimelineSpace.server!.baseURL,
|
||||
rootState.TimelineSpace.account!.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
const client = generator(req.server.sns, req.server.baseURL, req.account.accessToken, rootState.App.userAgent)
|
||||
const res = await client.saveMarkers({ home: { last_read_id: timeline[0].id } })
|
||||
return res.data
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue