feat: Implement bookmarks, close #1726
This commit is contained in:
parent
4e8a60ddef
commit
2113ab3d46
|
@ -55,5 +55,6 @@ module.exports = [
|
|||
{ id: 'fa-info-circle', src: 'src/thirdparty/font-awesome-svg-png/white/svg/info-circle.svg' },
|
||||
{ id: 'fa-crosshairs', src: 'src/thirdparty/font-awesome-svg-png/white/svg/crosshairs.svg' },
|
||||
{ id: 'fa-magic', src: 'src/thirdparty/font-awesome-svg-png/white/svg/magic.svg' },
|
||||
{ id: 'fa-hashtag', src: 'src/thirdparty/font-awesome-svg-png/white/svg/hashtag.svg' }
|
||||
{ id: 'fa-hashtag', src: 'src/thirdparty/font-awesome-svg-png/white/svg/hashtag.svg' },
|
||||
{ id: 'fa-bookmark', src: 'src/thirdparty/font-awesome-svg-png/white/svg/bookmark.svg' },
|
||||
]
|
||||
|
|
25
src/routes/_actions/bookmark.js
Normal file
25
src/routes/_actions/bookmark.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { store } from '../_store/store'
|
||||
import { toast } from '../_components/toast/toast'
|
||||
import { bookmarkStatus, unbookmarkStatus } from '../_api/bookmark'
|
||||
import { database } from '../_database/database'
|
||||
|
||||
export async function setStatusBookmarkedOrUnbookmarked (statusId, bookmarked) {
|
||||
const { currentInstance, accessToken } = store.get()
|
||||
try {
|
||||
if (bookmarked) {
|
||||
await bookmarkStatus(currentInstance, accessToken, statusId)
|
||||
} else {
|
||||
await unbookmarkStatus(currentInstance, accessToken, statusId)
|
||||
}
|
||||
if (bookmarked) {
|
||||
toast.say('Bookmarked status')
|
||||
} else {
|
||||
toast.say('Unbookmarked status')
|
||||
}
|
||||
store.setStatusBookmarked(currentInstance, statusId, bookmarked)
|
||||
await database.setStatusBookmarked(currentInstance, statusId, bookmarked)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.say(`Unable to ${bookmarked ? 'bookmark' : 'unbookmark'} status: ` + (e.message || ''))
|
||||
}
|
||||
}
|
|
@ -183,7 +183,7 @@ async function fetchTimelineItemsAndPossiblyFallBack () {
|
|||
online
|
||||
} = store.get()
|
||||
|
||||
if (currentTimeline === 'favorites') {
|
||||
if (currentTimeline === 'favorites' || currentTimeline === 'bookmarks') {
|
||||
// Always fetch favorites from the network, we currently don't have a good way of storing
|
||||
// these in IndexedDB because of "internal ID" system Mastodon uses to paginate these
|
||||
await fetchPagedItems(currentInstance, accessToken, currentTimeline)
|
||||
|
|
12
src/routes/_api/bookmark.js
Normal file
12
src/routes/_api/bookmark.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { post, WRITE_TIMEOUT } from '../_utils/ajax'
|
||||
import { auth, basename } from './utils'
|
||||
|
||||
export async function bookmarkStatus (instanceName, accessToken, statusId) {
|
||||
const url = `${basename(instanceName)}/api/v1/statuses/${statusId}/bookmark`
|
||||
return post(url, null, auth(accessToken), { timeout: WRITE_TIMEOUT })
|
||||
}
|
||||
|
||||
export async function unbookmarkStatus (instanceName, accessToken, statusId) {
|
||||
const url = `${basename(instanceName)}/api/v1/statuses/${statusId}/unbookmark`
|
||||
return post(url, null, auth(accessToken), { timeout: WRITE_TIMEOUT })
|
||||
}
|
|
@ -15,6 +15,8 @@ function getTimelineUrlPath (timeline) {
|
|||
return 'favourites'
|
||||
case 'direct':
|
||||
return 'conversations'
|
||||
case 'bookmarks':
|
||||
return 'bookmarks'
|
||||
}
|
||||
if (timeline.startsWith('tag/')) {
|
||||
return 'timelines/tag'
|
||||
|
@ -23,6 +25,7 @@ function getTimelineUrlPath (timeline) {
|
|||
} else if (timeline.startsWith('list/')) {
|
||||
return 'timelines/list'
|
||||
}
|
||||
throw new Error(`Invalid timeline type: ${timeline}`)
|
||||
}
|
||||
|
||||
export async function getTimeline (instanceName, accessToken, timeline, maxId, since, limit) {
|
||||
|
|
|
@ -18,6 +18,7 @@ import { close } from '../helpers/closeDialog'
|
|||
import { oncreate } from '../helpers/onCreateDialog'
|
||||
import { setAccountBlocked } from '../../../_actions/block'
|
||||
import { setStatusPinnedOrUnpinned } from '../../../_actions/pin'
|
||||
import { setStatusBookmarkedOrUnbookmarked } from '../../../_actions/bookmark'
|
||||
import { setConversationMuted } from '../../../_actions/muteConversation'
|
||||
import { copyText } from '../../../_actions/copyText'
|
||||
import { deleteAndRedraft } from '../../../_actions/deleteAndRedraft'
|
||||
|
@ -82,10 +83,11 @@ export default {
|
|||
muteConversationLabel: ({ mutingConversation }) => mutingConversation ? 'Unmute conversation' : 'Mute conversation',
|
||||
muteConversationIcon: ({ mutingConversation }) => mutingConversation ? '#fa-volume-up' : '#fa-volume-off',
|
||||
isPublicOrUnlisted: ({ visibility }) => visibility === 'public' || visibility === 'unlisted',
|
||||
bookmarkLabel: ({ status }) => status.bookmarked ? 'Unbookmark' : 'Bookmark',
|
||||
items: ({
|
||||
blockLabel, blocking, blockIcon, muteLabel, muteIcon, followLabel, followIcon,
|
||||
following, followRequested, pinLabel, isUser, visibility, mentionsUser, mutingConversation,
|
||||
muteConversationLabel, muteConversationIcon, supportsWebShare, isPublicOrUnlisted
|
||||
muteConversationLabel, muteConversationIcon, supportsWebShare, isPublicOrUnlisted, bookmarkLabel
|
||||
}) => ([
|
||||
isUser && {
|
||||
key: 'delete',
|
||||
|
@ -136,6 +138,11 @@ export default {
|
|||
key: 'copy',
|
||||
label: 'Copy link to toot',
|
||||
icon: '#fa-link'
|
||||
},
|
||||
{
|
||||
key: 'bookmark',
|
||||
label: bookmarkLabel,
|
||||
icon: '#fa-bookmark'
|
||||
}
|
||||
].filter(Boolean))
|
||||
},
|
||||
|
@ -169,6 +176,8 @@ export default {
|
|||
return this.onShare()
|
||||
case 'report':
|
||||
return this.onReport()
|
||||
case 'bookmark':
|
||||
return this.onBookmark()
|
||||
}
|
||||
},
|
||||
async onDeleteClicked () {
|
||||
|
@ -221,6 +230,11 @@ export default {
|
|||
const { status, account } = this.get()
|
||||
this.close()
|
||||
await reportStatusOrAccount(({ status, account }))
|
||||
},
|
||||
async onBookmark () {
|
||||
const { status } = this.get()
|
||||
this.close()
|
||||
await setStatusBookmarkedOrUnbookmarked(status.id, !status.bookmarked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,3 +51,9 @@ export async function setStatusMuted (instanceName, statusId, muted) {
|
|||
status.muted = muted
|
||||
})
|
||||
}
|
||||
|
||||
export async function setStatusBookmarked (instanceName, statusId, bookmarked) {
|
||||
return updateStatus(instanceName, statusId, status => {
|
||||
status.bookmarked = bookmarked
|
||||
})
|
||||
}
|
||||
|
|
32
src/routes/_pages/bookmarks.html
Normal file
32
src/routes/_pages/bookmarks.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
{#if $isUserLoggedIn}
|
||||
<TimelinePage timeline="bookmarks">
|
||||
{#if $pinnedPage !== '/bookmarks'}
|
||||
<DynamicPageBanner title="Bookmarks" icon="#fa-bookmark"/>
|
||||
{/if}
|
||||
</TimelinePage>
|
||||
{:else}
|
||||
<HiddenFromSSR>
|
||||
<FreeTextLayout>
|
||||
<h1>Bookmarks</h1>
|
||||
|
||||
<p>Your bookmarks will appear here when logged in.</p>
|
||||
</FreeTextLayout>
|
||||
</HiddenFromSSR>
|
||||
{/if}
|
||||
<script>
|
||||
import TimelinePage from '../_components/TimelinePage.html'
|
||||
import FreeTextLayout from '../_components/FreeTextLayout.html'
|
||||
import { store } from '../_store/store.js'
|
||||
import HiddenFromSSR from '../_components/HiddenFromSSR'
|
||||
import DynamicPageBanner from '../_components/DynamicPageBanner.html'
|
||||
|
||||
export default {
|
||||
store: () => store,
|
||||
components: {
|
||||
TimelinePage,
|
||||
FreeTextLayout,
|
||||
HiddenFromSSR,
|
||||
DynamicPageBanner
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -36,6 +36,12 @@
|
|||
pinnable="true"
|
||||
pinIndex={3}
|
||||
/>
|
||||
<PageListItem href="/bookmarks"
|
||||
label="Bookmarks"
|
||||
icon="#fa-bookmark"
|
||||
pinnable="true"
|
||||
pinIndex={4}
|
||||
/>
|
||||
</PageList>
|
||||
|
||||
{#if listsLength}
|
||||
|
|
|
@ -3,7 +3,8 @@ function getStatusModifications (store, instanceName) {
|
|||
statusModifications[instanceName] = statusModifications[instanceName] || {
|
||||
favorites: {},
|
||||
reblogs: {},
|
||||
pins: {}
|
||||
pins: {},
|
||||
bookmarks: {}
|
||||
}
|
||||
return statusModifications
|
||||
}
|
||||
|
@ -26,4 +27,8 @@ export function statusMixins (Store) {
|
|||
Store.prototype.setStatusPinned = function (instanceName, statusId, pinned) {
|
||||
setStatusModification(this, instanceName, statusId, 'pins', pinned)
|
||||
}
|
||||
|
||||
Store.prototype.setStatusBookmarked = function (instanceName, statusId, bookmarked) {
|
||||
setStatusModification(this, instanceName, statusId, 'bookmarks', bookmarked)
|
||||
}
|
||||
}
|
||||
|
|
20
src/routes/bookmarks.html
Normal file
20
src/routes/bookmarks.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<Title name="Bookmarks" />
|
||||
|
||||
<LazyPage {pageComponent} {params} />
|
||||
|
||||
<script>
|
||||
import Title from './_components/Title.html'
|
||||
import LazyPage from './_components/LazyPage.html'
|
||||
import pageComponent from './_pages/bookmarks.html'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
Title,
|
||||
LazyPage
|
||||
},
|
||||
data: () => ({
|
||||
pageComponent
|
||||
})
|
||||
}
|
||||
</script>
|
|
@ -1,2 +1 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1420 128q23 0 44 9 33 13 52.5 41t19.5 62v1289q0 34-19.5 62t-52.5 41q-19 8-44 8-48 0-83-32l-441-424-441 424q-36 33-83 33-23 0-44-9-33-13-52.5-41t-19.5-62v-1289q0-34 19.5-62t52.5-41q21-9 44-9h1048z" fill="#fff"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1792 1792"><path d="M1420 128q23 0 44 9 33 13 52.5 41t19.5 62v1289q0 34-19.5 62t-52.5 41q-19 8-44 8-48 0-83-32l-441-424-441 424q-36 33-83 33-23 0-44-9-33-13-52.5-41t-19.5-62v-1289q0-34 19.5-62t52.5-41q21-9 44-9h1048z" fill="#fff"/></svg>
|
||||
|
|
Before Width: | Height: | Size: 356 B After Width: | Height: | Size: 291 B |
Loading…
Reference in a new issue