mirror of
https://github.com/h3poteto/whalebird-desktop
synced 2025-02-02 10:26:50 +01:00
refs #1714 Create bookmarks timeline
This commit is contained in:
parent
375fb34873
commit
bd4bb90870
@ -65,6 +65,7 @@
|
||||
"notification": "Notification",
|
||||
"mention": "Mention",
|
||||
"favourite": "Favourite",
|
||||
"bookmark": "Bookmark",
|
||||
"follow_requests": "Follow Requests",
|
||||
"direct_messages": "Direct Messages",
|
||||
"local": "Local timeline",
|
||||
|
213
src/renderer/components/TimelineSpace/Contents/Bookmarks.vue
Normal file
213
src/renderer/components/TimelineSpace/Contents/Bookmarks.vue
Normal file
@ -0,0 +1,213 @@
|
||||
<template>
|
||||
<div id="bookmarks" v-shortkey="shortcutEnabled ? { next: ['j'] } : {}" @shortkey="handleKey">
|
||||
<div v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }" @shortkey="reload()"></div>
|
||||
<div class="bookmark" v-for="message in bookmarks" v-bind:key="message.id">
|
||||
<toot
|
||||
:message="message"
|
||||
:focused="message.uri === focusedId"
|
||||
:overlaid="modalOpened"
|
||||
v-on:update="updateToot"
|
||||
v-on:delete="deleteToot"
|
||||
@focusNext="focusNext"
|
||||
@focusPrev="focusPrev"
|
||||
@focusRight="focusSidebar"
|
||||
@selectToot="focusToot(message)"
|
||||
></toot>
|
||||
</div>
|
||||
<div class="loading-card" v-loading="lazyLoading" :element-loading-background="backgroundColor"></div>
|
||||
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
|
||||
<el-button type="primary" icon="el-icon-arrow-up" @click="upper" circle> </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import scrollTop from '@/components/utils/scroll'
|
||||
import Toot from '@/components/organisms/Toot'
|
||||
import reloadable from '~/src/renderer/components/mixins/reloadable'
|
||||
import { Event } from '~/src/renderer/components/event'
|
||||
|
||||
export default {
|
||||
name: 'bookmarks',
|
||||
components: { Toot },
|
||||
mixins: [reloadable],
|
||||
data() {
|
||||
return {
|
||||
heading: true,
|
||||
focusedId: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('TimelineSpace', {
|
||||
account: state => state.account
|
||||
}),
|
||||
...mapState('App', {
|
||||
backgroundColor: state => state.theme.background_color
|
||||
}),
|
||||
...mapState('TimelineSpace/HeaderMenu', {
|
||||
startReload: state => state.reload
|
||||
}),
|
||||
...mapState('TimelineSpace/Contents/SideBar', {
|
||||
openSideBar: state => state.openSideBar
|
||||
}),
|
||||
...mapState('TimelineSpace/Contents/Bookmarks', {
|
||||
bookmarks: state => state.bookmarks,
|
||||
lazyLoading: state => state.lazyLoading,
|
||||
filter: state => state.filter
|
||||
}),
|
||||
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
|
||||
shortcutEnabled: function () {
|
||||
return !this.focusedId && !this.modalOpened
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$store.commit('TimelineSpace/Contents/changeLoading', true)
|
||||
this.$store
|
||||
.dispatch('TimelineSpace/Contents/Bookmarks/fetchBookmarks', this.account)
|
||||
.catch(() => {
|
||||
this.$message({
|
||||
message: this.$t('message.bookmark_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
this.$store.commit('TimelineSpace/Contents/changeLoading', false)
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
document.getElementById('scrollable').addEventListener('scroll', this.onScroll)
|
||||
Event.$on('focus-timeline', () => {
|
||||
// If focusedId does not change, we have to refresh focusedId because Toot component watch change events.
|
||||
const previousFocusedId = this.focusedId
|
||||
this.focusedId = 0
|
||||
this.$nextTick(function () {
|
||||
this.focusedId = previousFocusedId
|
||||
})
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
Event.$off('focus-timeline')
|
||||
},
|
||||
destroyed() {
|
||||
this.$store.commit('TimelineSpace/Contents/Bookmarks/updateBookmarks', [])
|
||||
if (document.getElementById('scrollable') !== undefined && document.getElementById('scrollable') !== null) {
|
||||
document.getElementById('scrollable').removeEventListener('scroll', this.onScroll)
|
||||
document.getElementById('scrollable').scrollTop = 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
startReload: function (newState, oldState) {
|
||||
if (!oldState && newState) {
|
||||
this.reload().finally(() => {
|
||||
this.$store.commit('TimelineSpace/HeaderMenu/changeReload', false)
|
||||
})
|
||||
}
|
||||
},
|
||||
focusedId: function (newState, _oldState) {
|
||||
if (newState && this.heading) {
|
||||
this.heading = false
|
||||
} else if (newState === null && !this.heading) {
|
||||
this.heading = true
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateToot(message) {
|
||||
this.$store.commit('TimelineSpace/Contents/Bookmarks/updateToot', message)
|
||||
},
|
||||
deleteToot(message) {
|
||||
this.$store.commit('TimelineSpace/Contents/Bookmarks/deleteToot', message)
|
||||
},
|
||||
onScroll(event) {
|
||||
if (
|
||||
event.target.clientHeight + event.target.scrollTop >= document.getElementById('bookmarks').clientHeight - 10 &&
|
||||
!this.lazyloading
|
||||
) {
|
||||
this.$store.dispatch('TimelineSpace/Contents/Bookmarks/lazyFetchBookmarks', this.bookmarks[this.bookmarks.length - 1]).catch(() => {
|
||||
this.$message({
|
||||
message: this.$t('message.bookmark_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
}
|
||||
// for upper
|
||||
if (event.target.scrollTop > 10 && this.heading) {
|
||||
this.heading = false
|
||||
} else if (event.target.scrollTop <= 10 && !this.heading) {
|
||||
this.heading = true
|
||||
}
|
||||
},
|
||||
async reload() {
|
||||
this.$store.commit('TimelineSpace/changeLoading', true)
|
||||
try {
|
||||
const account = await this.reloadable()
|
||||
await this.$store.dispatch('TimelineSpace/Contents/Bookmarks/fetchBookmarks', account).catch(() => {
|
||||
this.$message({
|
||||
message: this.$t('message.bookmark_fetch_error'),
|
||||
type: 'error'
|
||||
})
|
||||
})
|
||||
} finally {
|
||||
this.$store.commit('TimelineSpace/changeLoading', false)
|
||||
}
|
||||
},
|
||||
upper() {
|
||||
scrollTop(document.getElementById('scrollable'), 0)
|
||||
this.focusedId = null
|
||||
},
|
||||
focusNext() {
|
||||
const currentIndex = this.bookmarks.findIndex(toot => this.focusedId === toot.uri)
|
||||
if (currentIndex === -1) {
|
||||
this.focusedId = this.bookmarks[0].uri
|
||||
} else if (currentIndex < this.bookmarks.length) {
|
||||
this.focusedId = this.bookmarks[currentIndex + 1].uri
|
||||
}
|
||||
},
|
||||
focusPrev() {
|
||||
const currentIndex = this.bookmarks.findIndex(toot => this.focusedId === toot.uri)
|
||||
if (currentIndex === 0) {
|
||||
this.focusedId = null
|
||||
} else if (currentIndex > 0) {
|
||||
this.focusedId = this.bookmarks[currentIndex - 1].uri
|
||||
}
|
||||
},
|
||||
focusToot(message) {
|
||||
this.focusedId = message.id
|
||||
},
|
||||
focusSidebar() {
|
||||
Event.$emit('focus-sidebar')
|
||||
},
|
||||
handleKey(event) {
|
||||
switch (event.srcKey) {
|
||||
case 'next':
|
||||
this.focusedId = this.bookmarks[0].uri
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.loading-card {
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.loading-card:empty {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.upper {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.upper-with-side-bar {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: calc(20px + var(--current-sidebar-width));
|
||||
transition: all 0.5s;
|
||||
}
|
||||
</style>
|
@ -74,7 +74,7 @@ export default {
|
||||
this.$store.dispatch('TimelineSpace/HeaderMenu/setupLoading')
|
||||
},
|
||||
watch: {
|
||||
$route: function() {
|
||||
$route: function () {
|
||||
this.channelName()
|
||||
this.loadFilter()
|
||||
}
|
||||
@ -94,6 +94,9 @@ export default {
|
||||
case 'favourites':
|
||||
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.favourite'))
|
||||
break
|
||||
case 'bookmarks':
|
||||
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.bookmark'))
|
||||
break
|
||||
case 'mentions':
|
||||
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.mention'))
|
||||
break
|
||||
@ -142,6 +145,7 @@ export default {
|
||||
case 'notifications':
|
||||
case 'mentions':
|
||||
case 'favourites':
|
||||
case 'bookmarks':
|
||||
case 'local':
|
||||
case 'public':
|
||||
case 'tag':
|
||||
@ -159,6 +163,7 @@ export default {
|
||||
case 'notifications':
|
||||
case 'mentions':
|
||||
case 'favourites':
|
||||
case 'bookmarks':
|
||||
case 'local':
|
||||
case 'public':
|
||||
case 'tag':
|
||||
|
@ -30,6 +30,7 @@ import TimelineSpaceContentsListsIndex from '@/components/TimelineSpace/Contents
|
||||
import TimelineSpaceContentsListsEdit from '@/components/TimelineSpace/Contents/Lists/Edit.vue'
|
||||
import TimelineSpaceContentsListsShow from '@/components/TimelineSpace/Contents/Lists/Show.vue'
|
||||
import TimelineSpaceContentsFollowRequests from '@/components/TimelineSpace/Contents/FollowRequests.vue'
|
||||
import TimelineSpaceContentsBookmarks from '@/components/TimelineSpace/Contents/Bookmarks.vue'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
@ -132,6 +133,11 @@ const router = new Router({
|
||||
name: 'favourites',
|
||||
component: TimelineSpaceContentsFavourites
|
||||
},
|
||||
{
|
||||
path: 'bookmarks',
|
||||
name: 'bookmarks',
|
||||
component: TimelineSpaceContentsBookmarks
|
||||
},
|
||||
{
|
||||
path: 'local',
|
||||
name: 'local',
|
||||
|
@ -2,6 +2,7 @@ import SideBar, { SideBarModuleState } from './Contents/SideBar'
|
||||
import Home, { HomeState } from './Contents/Home'
|
||||
import Notifications, { NotificationsState } from './Contents/Notifications'
|
||||
import Favourites, { FavouritesState } from './Contents/Favourites'
|
||||
import Bookmarks, { BookmarksState } from './Contents/Bookmarks'
|
||||
import Local, { LocalState } from './Contents/Local'
|
||||
import Public, { PublicState } from './Contents/Public'
|
||||
import Search, { SearchModuleState } from './Contents/Search'
|
||||
@ -24,6 +25,7 @@ type ContentsModule = {
|
||||
Mentions: MentionsState
|
||||
DirectMessages: DirectMessagesState
|
||||
Favourites: FavouritesState
|
||||
Bookmarks: BookmarksState
|
||||
Local: LocalState
|
||||
Public: PublicState
|
||||
Search: SearchModuleState
|
||||
@ -61,6 +63,7 @@ const Contents: Module<ContentsState, RootState> = {
|
||||
Home,
|
||||
Notifications,
|
||||
Favourites,
|
||||
Bookmarks,
|
||||
Local,
|
||||
DirectMessages,
|
||||
Mentions,
|
||||
|
128
src/renderer/store/TimelineSpace/Contents/Bookmarks.ts
Normal file
128
src/renderer/store/TimelineSpace/Contents/Bookmarks.ts
Normal file
@ -0,0 +1,128 @@
|
||||
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'
|
||||
|
||||
export type BookmarksState = {
|
||||
bookmarks: Array<Entity.Status>
|
||||
lazyLoading: boolean
|
||||
maxId: string | null
|
||||
}
|
||||
|
||||
const state = (): BookmarksState => ({
|
||||
bookmarks: [],
|
||||
lazyLoading: false,
|
||||
maxId: null
|
||||
})
|
||||
|
||||
export const MUTATION_TYPES = {
|
||||
UPDATE_BOOKMARKS: 'updateBookmarks',
|
||||
INSERT_BOOKMARKS: 'insertBookmarks',
|
||||
UPDATE_TOOT: 'updateToot',
|
||||
DELETE_TOOT: 'deleteToot',
|
||||
CHANGE_LAZY_LOADING: 'changeLazyLoading',
|
||||
CHANGE_MAX_ID: 'changeMaxId'
|
||||
}
|
||||
|
||||
const mutations: MutationTree<BookmarksState> = {
|
||||
[MUTATION_TYPES.UPDATE_BOOKMARKS]: (state, bookmarks: Array<Entity.Status>) => {
|
||||
state.bookmarks = bookmarks
|
||||
},
|
||||
[MUTATION_TYPES.INSERT_BOOKMARKS]: (state, bookmarks: Array<Entity.Status>) => {
|
||||
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_LAZY_LOADING]: (state, value: boolean) => {
|
||||
state.lazyLoading = value
|
||||
},
|
||||
[MUTATION_TYPES.CHANGE_MAX_ID]: (state, id: string | null) => {
|
||||
state.maxId = id
|
||||
}
|
||||
}
|
||||
|
||||
const actions: ActionTree<BookmarksState, RootState> = {
|
||||
fetchBookmarks: async ({ commit, rootState }, account: LocalAccount): Promise<Array<Entity.Status>> => {
|
||||
const client = generator(rootState.TimelineSpace.sns, account.baseURL, account.accessToken, rootState.App.userAgent)
|
||||
const res = await client.getBookmarks({ limit: 40 })
|
||||
commit(MUTATION_TYPES.UPDATE_BOOKMARKS, res.data)
|
||||
// Parse link header
|
||||
try {
|
||||
const link = parse(res.headers.link)
|
||||
if (link !== null) {
|
||||
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
|
||||
},
|
||||
laxyFetchBookmarks: async ({ state, commit, rootState }): Promise<Array<Entity.Status> | null> => {
|
||||
if (state.lazyLoading) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
if (!state.maxId) {
|
||||
return Promise.resolve(null)
|
||||
}
|
||||
commit(MUTATION_TYPES.CHANGE_LAZY_LOADING, true)
|
||||
const client = generator(
|
||||
rootState.TimelineSpace.sns,
|
||||
rootState.TimelineSpace.account.baseURL,
|
||||
rootState.TimelineSpace.account.accessToken,
|
||||
rootState.App.userAgent
|
||||
)
|
||||
const res = await client.getFavourites({ max_id: state.maxId, limit: 40 }).finally(() => {
|
||||
commit(MUTATION_TYPES.CHANGE_LAZY_LOADING, false)
|
||||
})
|
||||
commit(MUTATION_TYPES.INSERT_BOOKMARKS, res.data)
|
||||
// Parse link header
|
||||
try {
|
||||
const link = parse(res.headers.link)
|
||||
if (link !== null) {
|
||||
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<BookmarksState, RootState> = {
|
||||
namespaced: true,
|
||||
state: state,
|
||||
mutations: mutations,
|
||||
actions: actions
|
||||
}
|
||||
|
||||
export default Bookmark
|
Loading…
x
Reference in New Issue
Block a user