diff --git a/bin/svgs.js b/bin/svgs.js
index 65c488cf..ee2bb94d 100644
--- a/bin/svgs.js
+++ b/bin/svgs.js
@@ -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' },
]
diff --git a/src/routes/_actions/bookmark.js b/src/routes/_actions/bookmark.js
new file mode 100644
index 00000000..06431865
--- /dev/null
+++ b/src/routes/_actions/bookmark.js
@@ -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 || ''))
+ }
+}
diff --git a/src/routes/_actions/timeline.js b/src/routes/_actions/timeline.js
index 0be5ea9e..3a3909f9 100644
--- a/src/routes/_actions/timeline.js
+++ b/src/routes/_actions/timeline.js
@@ -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)
diff --git a/src/routes/_api/bookmark.js b/src/routes/_api/bookmark.js
new file mode 100644
index 00000000..3fde63d2
--- /dev/null
+++ b/src/routes/_api/bookmark.js
@@ -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 })
+}
diff --git a/src/routes/_api/timelines.js b/src/routes/_api/timelines.js
index 55270a96..fe436dfa 100644
--- a/src/routes/_api/timelines.js
+++ b/src/routes/_api/timelines.js
@@ -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) {
diff --git a/src/routes/_components/dialog/components/StatusOptionsDialog.html b/src/routes/_components/dialog/components/StatusOptionsDialog.html
index 05509e9d..d8d75588 100644
--- a/src/routes/_components/dialog/components/StatusOptionsDialog.html
+++ b/src/routes/_components/dialog/components/StatusOptionsDialog.html
@@ -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)
}
}
}
diff --git a/src/routes/_database/timelines/updateStatus.js b/src/routes/_database/timelines/updateStatus.js
index 8d4b63c3..3da25a5b 100644
--- a/src/routes/_database/timelines/updateStatus.js
+++ b/src/routes/_database/timelines/updateStatus.js
@@ -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
+ })
+}
diff --git a/src/routes/_pages/bookmarks.html b/src/routes/_pages/bookmarks.html
new file mode 100644
index 00000000..2c041eea
--- /dev/null
+++ b/src/routes/_pages/bookmarks.html
@@ -0,0 +1,32 @@
+{#if $isUserLoggedIn}
+ Your bookmarks will appear here when logged in.Bookmarks
+
+