From 7ff45dfb3f0bb502ef884df34a684b35c13b36d4 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Sat, 14 Apr 2018 22:18:48 -0700 Subject: [PATCH] mute and unmute accounts (#127) --- bin/svgs.js | 5 +- routes/_actions/mute.js | 27 ++++++ routes/_api/mute.js | 12 +++ .../AccountProfileOptionsDialog.html | 83 ++++++++++++++++--- .../components/StatusOptionsDialog.html | 59 +++++++++---- .../showAccountProfileOptionsDialog.js | 5 +- .../_components/profile/AccountProfile.html | 2 +- .../profile/AccountProfileDetails.html | 3 +- templates/2xx.html | 3 +- tests/spec/113-block-unblock.js | 7 +- tests/spec/114-mute-unmute.js | 43 ++++++++++ tests/spec/115-follow-unfollow.js | 28 +++++++ 12 files changed, 240 insertions(+), 37 deletions(-) create mode 100644 routes/_actions/mute.js create mode 100644 routes/_api/mute.js create mode 100644 tests/spec/114-mute-unmute.js create mode 100644 tests/spec/115-follow-unfollow.js diff --git a/bin/svgs.js b/bin/svgs.js index 8855872..0366bb2 100644 --- a/bin/svgs.js +++ b/bin/svgs.js @@ -24,7 +24,6 @@ module.exports = [ {id: 'fa-paperclip', src: 'node_modules/font-awesome-svg-png/white/svg/paperclip.svg', title: 'Paperclip'}, {id: 'fa-thumb-tack', src: 'node_modules/font-awesome-svg-png/white/svg/thumb-tack.svg', title: 'Thumbtack'}, {id: 'fa-bars', src: 'node_modules/font-awesome-svg-png/white/svg/bars.svg', title: 'List'}, - {id: 'fa-volume-off', src: 'node_modules/font-awesome-svg-png/white/svg/volume-off.svg', title: 'Volume off'}, {id: 'fa-ban', src: 'node_modules/font-awesome-svg-png/white/svg/ban.svg', title: 'Ban'}, {id: 'fa-camera', src: 'node_modules/font-awesome-svg-png/white/svg/camera.svg', title: 'Add media'}, {id: 'fa-smile', src: 'node_modules/font-awesome-svg-png/white/svg/smile-o.svg', title: 'Custom emoji'}, @@ -33,5 +32,7 @@ module.exports = [ {id: 'fa-trash', src: 'node_modules/font-awesome-svg-png/white/svg/trash-o.svg', title: 'Delete'}, {id: 'fa-hourglass', src: 'node_modules/font-awesome-svg-png/white/svg/hourglass.svg', title: 'Follow requested'}, {id: 'fa-pencil', src: 'node_modules/font-awesome-svg-png/white/svg/pencil.svg', title: 'Compose'}, - {id: 'fa-times', src: 'node_modules/font-awesome-svg-png/white/svg/times.svg', title: 'Close'} + {id: 'fa-times', src: 'node_modules/font-awesome-svg-png/white/svg/times.svg', title: 'Close'}, + {id: 'fa-volume-off', src: 'node_modules/font-awesome-svg-png/white/svg/volume-off.svg', title: 'Mute'}, + {id: 'fa-volume-up', src: 'node_modules/font-awesome-svg-png/white/svg/volume-up.svg', title: 'Unmute'} ] diff --git a/routes/_actions/mute.js b/routes/_actions/mute.js new file mode 100644 index 0000000..a0bc6e4 --- /dev/null +++ b/routes/_actions/mute.js @@ -0,0 +1,27 @@ +import { store } from '../_store/store' +import { muteAccount, unmuteAccount } from '../_api/mute' +import { toast } from '../_utils/toast' +import { updateProfileAndRelationship } from './accounts' + +export async function setAccountMuted (accountId, mute, toastOnSuccess) { + let instanceName = store.get('currentInstance') + let accessToken = store.get('accessToken') + try { + if (mute) { + await muteAccount(instanceName, accessToken, accountId) + } else { + await unmuteAccount(instanceName, accessToken, accountId) + } + await updateProfileAndRelationship(accountId) + if (toastOnSuccess) { + if (mute) { + toast.say('Muted account') + } else { + toast.say('Unmuted account') + } + } + } catch (e) { + console.error(e) + toast.say(`Unable to ${mute ? 'mute' : 'unmute'} account: ` + (e.message || '')) + } +} diff --git a/routes/_api/mute.js b/routes/_api/mute.js new file mode 100644 index 0000000..9acb76a --- /dev/null +++ b/routes/_api/mute.js @@ -0,0 +1,12 @@ +import { auth, basename } from './utils' +import { postWithTimeout } from '../_utils/ajax' + +export async function muteAccount (instanceName, accessToken, accountId) { + let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/mute` + return postWithTimeout(url, null, auth(accessToken)) +} + +export async function unmuteAccount (instanceName, accessToken, accountId) { + let url = `${basename(instanceName)}/api/v1/accounts/${accountId}/unmute` + return postWithTimeout(url, null, auth(accessToken)) +} diff --git a/routes/_components/dialog/components/AccountProfileOptionsDialog.html b/routes/_components/dialog/components/AccountProfileOptionsDialog.html index ee10527..9a187b2 100644 --- a/routes/_components/dialog/components/AccountProfileOptionsDialog.html +++ b/routes/_components/dialog/components/AccountProfileOptionsDialog.html @@ -16,6 +16,8 @@ import { show } from '../helpers/showDialog' import { close } from '../helpers/closeDialog' import { oncreate } from '../helpers/onCreateDialog' import { setAccountBlocked } from '../../../_actions/block' +import { setAccountMuted } from '../../../_actions/mute' +import { setAccountFollowed } from '../../../_actions/follow' export default { oncreate, @@ -24,21 +26,62 @@ export default { id: createDialogId() }), computed: { - blocking: (relationship) => relationship && relationship.blocking, - items: (account, blocking) => ( - [ - { + // begin account data copypasta + verifyCredentialsId: (verifyCredentials) => verifyCredentials.id, + following: (relationship) => relationship && relationship.following, + followRequested: (relationship) => relationship && relationship.requested, + accountId: (account) => account && account.id, + acct: (account) => account.acct, + muting: (relationship) => relationship.muting, + blocking: (relationship) => relationship.blocking, + followLabel: (following, followRequested, account, acct) => { + if (typeof following === 'undefined' || !account) { + return '' + } + return (following || followRequested) + ? `Unfollow @${acct}` + : `Follow @${acct}` + }, + followIcon: (following, followRequested) => { + return following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus' + }, + blockLabel: (blocking, acct) => { + return blocking ? `Unblock @${acct}` : `Block @${acct}` + }, + blockIcon: (blocking) => blocking ? '#fa-unlock' : '#fa-ban', + muteLabel: (muting, acct) => { + return muting ? `Unmute @${acct}` : `Mute @${acct}` + }, + muteIcon: (muting) => muting ? '#fa-volume-up' : '#fa-volume-off', + // end account data copypasta + items: (blockLabel, blocking, blockIcon, muteLabel, muteIcon, + followLabel, followIcon, following, followRequested, + accountId, verifyCredentialsId, acct) => { + let isUser = accountId === verifyCredentialsId + return [ + !isUser && { key: 'mention', - label: `Mention @${account.acct}`, + label: `Mention @${acct}`, icon: '#fa-comments' }, - { + !isUser && !blocking && { + key: 'follow', + label: followLabel, + icon: followIcon + }, + !isUser && { key: 'block', - label: blocking ? `Unblock @${account.acct}` : `Block @${account.acct}`, - icon: blocking ? '#fa-unlock' : '#fa-ban' + label: blockLabel, + icon: blockIcon + }, + !isUser && !blocking && { + key: 'mute', + label: muteLabel, + icon: muteIcon } - ] - ) + + ].filter(Boolean) + } }, methods: { show, @@ -47,25 +90,41 @@ export default { switch (item.key) { case 'mention': return this.onMentionClicked() + case 'follow': + return this.onFollowClicked() case 'block': return this.onBlockClicked() + case 'mute': + return this.onMuteClicked() } }, async onMentionClicked() { - let account = this.get('account') + let acct = this.get('acct') this.store.setComposeData('dialog', { - text: `@${account.acct} ` + text: `@${acct} ` }) let dialogs = await importDialogs() dialogs.showComposeDialog() this.close() }, + async onFollowClicked() { + let accountId = this.get('accountId') + let following = this.get('following') + this.close() + await setAccountFollowed(accountId, !following, true) + }, async onBlockClicked() { let account = this.get('account') let blocking = this.get('blocking') let accountId = account.id this.close() await setAccountBlocked(accountId, !blocking, true) + }, + async onMuteClicked() { + let accountId = this.get('accountId') + let muting = this.get('muting') + this.close() + await setAccountMuted(accountId, !muting, true) } }, components: { diff --git a/routes/_components/dialog/components/StatusOptionsDialog.html b/routes/_components/dialog/components/StatusOptionsDialog.html index c85fb5a..d544590 100644 --- a/routes/_components/dialog/components/StatusOptionsDialog.html +++ b/routes/_components/dialog/components/StatusOptionsDialog.html @@ -16,6 +16,7 @@ import { show } from '../helpers/showDialog' import { close } from '../helpers/closeDialog' import { oncreate } from '../helpers/onCreateDialog' import { setAccountBlocked } from '../../../_actions/block' +import { setAccountMuted } from '../../../_actions/mute' export default { oncreate, @@ -23,44 +24,62 @@ export default { relationship: ($currentAccountRelationship) => $currentAccountRelationship, account: ($currentAccountProfile) => $currentAccountProfile, verifyCredentials: ($currentVerifyCredentials) => $currentVerifyCredentials, + // begin account data copypasta verifyCredentialsId: (verifyCredentials) => verifyCredentials.id, following: (relationship) => relationship && relationship.following, followRequested: (relationship) => relationship && relationship.requested, accountId: (account) => account && account.id, + acct: (account) => account.acct, + muting: (relationship) => relationship.muting, blocking: (relationship) => relationship.blocking, - followLabel: (following, followRequested, account) => { + followLabel: (following, followRequested, account, acct) => { if (typeof following === 'undefined' || !account) { return '' } return (following || followRequested) - ? `Unfollow @${account.acct}` - : `Follow @${account.acct}` + ? `Unfollow @${acct}` + : `Follow @${acct}` }, - blockLabel: (blocking, account) => { - return blocking ? `Unblock @${account.acct}` : `Block @${account.acct}` + followIcon: (following, followRequested) => { + return following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus' }, - items: (blockLabel, blocking, followLabel, following, followRequested, accountId, verifyCredentialsId) => ( - [ - accountId === verifyCredentialsId && - { + blockLabel: (blocking, acct) => { + return blocking ? `Unblock @${acct}` : `Block @${acct}` + }, + blockIcon: (blocking) => blocking ? '#fa-unlock' : '#fa-ban', + muteLabel: (muting, acct) => { + return muting ? `Unmute @${acct}` : `Mute @${acct}` + }, + muteIcon: (muting) => muting ? '#fa-volume-up' : '#fa-volume-off', + // end account data copypasta + items: (blockLabel, blocking, blockIcon, muteLabel, muteIcon, + followLabel, followIcon, following, followRequested, + accountId, verifyCredentialsId) => { + let isUser = accountId === verifyCredentialsId + return [ + isUser && { key: 'delete', label: 'Delete', icon: '#fa-trash' }, - accountId !== verifyCredentialsId && !blocking && - { + !isUser && !blocking && { key: 'follow', label: followLabel, - icon: following ? '#fa-user-times' : followRequested ? '#fa-hourglass' : '#fa-user-plus' + icon: followIcon }, - accountId !== verifyCredentialsId && - { + !isUser && { key: 'block', label: blockLabel, - icon: blocking ? '#fa-unlock' : '#fa-ban' + icon: blockIcon + }, + !isUser && !blocking && { + key: 'mute', + label: muteLabel, + icon: muteIcon } + ].filter(Boolean) - ) + } }, components: { ModalDialog, @@ -78,6 +97,8 @@ export default { return this.onFollowClicked() case 'block': return this.onBlockClicked() + case 'mute': + return this.onMuteClicked() } }, async onDeleteClicked() { @@ -96,6 +117,12 @@ export default { let blocking = this.get('blocking') this.close() await setAccountBlocked(accountId, !blocking, true) + }, + async onMuteClicked() { + let accountId = this.get('accountId') + let muting = this.get('muting') + this.close() + await setAccountMuted(accountId, !muting, true) } } } diff --git a/routes/_components/dialog/creators/showAccountProfileOptionsDialog.js b/routes/_components/dialog/creators/showAccountProfileOptionsDialog.js index 51e38e4..3dd964f 100644 --- a/routes/_components/dialog/creators/showAccountProfileOptionsDialog.js +++ b/routes/_components/dialog/creators/showAccountProfileOptionsDialog.js @@ -2,7 +2,7 @@ import AccountProfileOptionsDialog from '../components/AccountProfileOptionsDial import { createDialogElement } from '../helpers/createDialogElement' import { createDialogId } from '../helpers/createDialogId' -export function showAccountProfileOptionsDialog (account, relationship) { +export function showAccountProfileOptionsDialog (account, relationship, verifyCredentials) { let dialog = new AccountProfileOptionsDialog({ target: createDialogElement(), data: { @@ -10,7 +10,8 @@ export function showAccountProfileOptionsDialog (account, relationship) { label: 'Profile options dialog', title: '', account: account, - relationship: relationship + relationship: relationship, + verifyCredentials: verifyCredentials } }) dialog.show() diff --git a/routes/_components/profile/AccountProfile.html b/routes/_components/profile/AccountProfile.html index 629ad27..20260aa 100644 --- a/routes/_components/profile/AccountProfile.html +++ b/routes/_components/profile/AccountProfile.html @@ -5,7 +5,7 @@ - + diff --git a/routes/_components/profile/AccountProfileDetails.html b/routes/_components/profile/AccountProfileDetails.html index 86f0f75..7f7a9e3 100644 --- a/routes/_components/profile/AccountProfileDetails.html +++ b/routes/_components/profile/AccountProfileDetails.html @@ -120,8 +120,9 @@ async onMoreOptionsClick() { let account = this.get('account') let relationship = this.get('relationship') + let verifyCredentials = this.get('verifyCredentials') let dialogs = await importDialogs() - dialogs.showAccountProfileOptionsDialog(account, relationship) + dialogs.showAccountProfileOptionsDialog(account, relationship, verifyCredentials) } }, components: { diff --git a/templates/2xx.html b/templates/2xx.html index 0d6d3b1..539857a 100644 --- a/templates/2xx.html +++ b/templates/2xx.html @@ -106,7 +106,6 @@ if (!localStorage.store_currentInstance) { Paperclip Thumbtack List -Volume off Ban Add media Custom emoji @@ -116,6 +115,8 @@ if (!localStorage.store_currentInstance) { Follow requested Compose Close +Mute +Unmute