feat: support account "bell" notifications

Fixes #1961
This commit is contained in:
Alexander Yakovlev 2022-04-28 16:42:12 +07:00 committed by Nolan Lawson
parent 54a11778da
commit 2e9afd711f
5 changed files with 147 additions and 2 deletions

View file

@ -309,6 +309,10 @@ export default {
true {(follow requested)}
other {}
}`,
notifyLabel: `Subscribe`,
denotifyLabel: `Unsubscribe`,
subscribedAccount: 'Subscribed to account',
unsubscribedAccount: 'Unsubscribed to account',
unblock: 'Unblock',
nameAndFollowing: 'Name and following',
clickToSeeAvatar: 'Click to see avatar',
@ -631,6 +635,8 @@ export default {
unableToShowReblogs: 'Unable to show boosts: {error}',
unableToHideReblogs: 'Unable to hide boosts: {error}',
unableToShare: 'Unable to share: {error}',
unableToSubscribe: 'Unable to subscribe: {error}',
unableToUnsubscribe: 'Unable to unsubscribe: {error}',
showingOfflineContent: 'Internet request failed. Showing offline content.',
youAreOffline: 'You seem to be offline. You can still read toots while offline.',
// Snackbar UI

View file

@ -0,0 +1,27 @@
import { store } from '../_store/store.js'
import { notifyAccount, denotifyAccount } from '../_api/notify.js'
import { toast } from '../_components/toast/toast.js'
import { updateLocalRelationship } from './accounts.js'
import { formatIntl } from '../_utils/formatIntl.js'
export async function setAccountNotify (accountId, notify, toastOnSuccess) {
const { currentInstance, accessToken } = store.get()
try {
let relationship
if (notify) {
relationship = await notifyAccount(currentInstance, accessToken, accountId)
} else {
relationship = await denotifyAccount(currentInstance, accessToken, accountId)
}
await updateLocalRelationship(currentInstance, accountId, relationship)
if (toastOnSuccess) {
/* no await */ toast.say(follow ? 'intl.subscribedAccount' : 'intl.unsubscribedAccount')
}
} catch (e) {
console.error(e)
/* no await */ toast.say(follow
? formatIntl('intl.unableToSubscribe', { error: (e.message || '') })
: formatIntl('intl.unableToUnsubscribe', { error: (e.message || '') })
)
}
}

16
src/routes/_api/notify.js Normal file
View file

@ -0,0 +1,16 @@
import { post, WRITE_TIMEOUT } from '../_utils/ajax.js'
import { auth, basename } from './utils.js'
export async function notifyAccount (instanceName, accessToken, accountId) {
const url = `${basename(instanceName)}/api/v1/accounts/${accountId}/follow`
return post(url, {
"notify": true,
}, auth(accessToken), { timeout: WRITE_TIMEOUT })
}
export async function denotifyAccount (instanceName, accessToken, accountId) {
const url = `${basename(instanceName)}/api/v1/accounts/${accountId}/follow`
return post(url, {
"notify": false
}, auth(accessToken), { timeout: WRITE_TIMEOUT })
}

View file

@ -8,6 +8,7 @@
<div class="account-profile-grid-wrapper">
<div class="account-profile-grid">
<AccountProfileHeader {account} {relationship} {verifyCredentials} />
<AccountProfileNotify {account} {relationship} {verifyCredentials} />
<AccountProfileFollow {account} {relationship} {verifyCredentials} />
<AccountProfileNote {account} />
<AccountProfileMeta {account} />
@ -37,7 +38,7 @@
display: grid;
grid-template-areas: "avatar name label followed-by follow"
"avatar username username username follow"
"avatar note note note follow"
"avatar note note note notify"
"meta meta meta meta meta"
"details details details details details";
grid-template-columns: min-content auto 1fr 1fr min-content;
@ -71,7 +72,7 @@
grid-template-areas: "avatar name follow"
"avatar label follow"
"avatar username follow"
"avatar followed-by follow"
"avatar followed-by notify"
"note note note"
"meta meta meta"
"details details details";
@ -97,6 +98,7 @@
"username username"
"followed-by followed-by"
"follow follow"
"notify notify"
"note note"
"meta meta"
"details details";
@ -108,6 +110,7 @@
</style>
<script>
import AccountProfileHeader from './AccountProfileHeader.html'
import AccountProfileNotify from './AccountProfileNotify.html'
import AccountProfileFollow from './AccountProfileFollow.html'
import AccountProfileNote from './AccountProfileNote.html'
import AccountProfileMeta from './AccountProfileMeta.html'
@ -144,6 +147,7 @@
AccountProfileHeader,
AccountProfileFollow,
AccountProfileNote,
AccountProfileNotify,
AccountProfileMeta,
AccountProfileDetails,
AccountProfileMovedBanner,

View file

@ -0,0 +1,92 @@
<div class="account-profile-notify {shown ? 'shown' : ''}">
<!--
The bell notification button (Mastodon 3.3+)
Shows if we're getting notifications or not.
It is not possible to turn on notifications for accounts you don't follow.
Also the instance can just have no support for this feature.
-->
<IconButton
className="account-profile-notify-icon-button"
{label}
pressedLabel="{intl.denotifyLabel}"
{href}
big={!$isVeryTinyMobileSize}
on:click="onNotifyButtonClick(event)"
ref:icon
/>
</div>
<style>
.account-profile-notify {
grid-area: notify;
align-self: flex-start;
display: none;
}
.account-profile-notify.shown {
display: block;
}
@media (max-width: 240px) {
.account-profile-notify {
justify-self: flex-end;
}
}
</style>
<script>
import IconButton from '../IconButton.html'
import { FOLLOW_BUTTON_ANIMATION } from '../../_static/animations.js'
import { store } from '../../_store/store.js'
import { setAccountNotify } from '../../_actions/notify.js'
import { formatIntl } from '../../_utils/formatIntl.js'
export default {
methods: {
oncreate () {
if (process.browser) {
window.__button = this
}
},
async onNotifyButtonClick (e) {
e.preventDefault()
e.stopPropagation()
const {
account,
accountId,
notifying
} = this.get()
if (notifying) { // unblock
await setAccountNotify(accountId, false)
} else { // follow/unfollow
this.refs.icon.animate(FOLLOW_BUTTON_ANIMATION)
await setAccountNotify(accountId, true)
}
}
},
store: () => store,
data: () => ({
}),
computed: {
accountId: ({ account }) => account.id,
notifying: ({ relationship }) => {
return relationship && relationship.notifying
},
href: ({ notifying }) => {
if (notifying) {
return '#fa-bell-ringing'
}
return '#fa-bell-o'
},
label: ({ notifying }) => {
if (notifying) {
return formatIntl('intl.notifyLabel')
}
return formatIntl('intl.denotifyLabel')
},
shown: ({ verifyCredentials, relationship }) => (
verifyCredentials && relationship && verifyCredentials.id !== relationship.id && relationship.following
),
},
components: {
IconButton
}
}
</script>