Merge branch '482-subscribe-user' into 'develop'

Implement ability to subscribe to a user

Closes #482

See merge request pleroma/pleroma-fe!771
This commit is contained in:
HJ 2019-07-15 06:50:31 +00:00
commit 0c06410584
26 changed files with 212 additions and 146 deletions

View file

@ -283,6 +283,31 @@ i[class*=icon-] {
color: var(--icon, $fallback--icon)
}
.btn-block {
display: block;
width: 100%;
}
.btn-group {
position: relative;
display: inline-flex;
vertical-align: middle;
button {
position: relative;
flex: 1 1 auto;
&:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&:not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
}
.container {
display: flex;

View file

@ -100,7 +100,7 @@
<!-- eslint-disable vue/no-v-html -->
<h1><a :href="attachment.url">{{ attachment.oembed.title }}</a></h1>
<div v-html="attachment.oembed.oembedHTML" />
<!-- eslint-enabled vue/no-v-html -->
<!-- eslint-enable vue/no-v-html -->
</div>
</div>
</div>

View file

@ -1,8 +1,5 @@
<template>
<div
class="block"
style="position: relative"
>
<div>
<Popper
trigger="click"
append-to-body
@ -131,6 +128,7 @@
</div>
<button
slot="reference"
class="btn btn-default btn-block"
:class="{ pressed: showDropDown }"
@click="toggleMenu"
>

View file

@ -3,7 +3,7 @@
:disabled="progress || disabled"
@click="onClick"
>
<template v-if="progress">
<template v-if="progress && $slots.progress">
<slot name="progress" />
</template>
<template v-else>

View file

@ -1,5 +1,6 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import RemoteFollow from '../remote_follow/remote_follow.vue'
import ProgressButton from '../progress_button/progress_button.vue'
import ModerationTools from '../moderation_tools/moderation_tools.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
@ -104,7 +105,8 @@ export default {
components: {
UserAvatar,
RemoteFollow,
ModerationTools
ModerationTools,
ProgressButton
},
methods: {
followUser () {
@ -135,6 +137,12 @@ export default {
unmuteUser () {
this.$store.dispatch('unmuteUser', this.user.id)
},
subscribeUser () {
return this.$store.dispatch('subscribeUser', this.user.id)
},
unsubscribeUser () {
return this.$store.dispatch('unsubscribeUser', this.user.id)
},
setProfileView (v) {
if (this.switcher) {
const store = this.$store

View file

@ -112,101 +112,120 @@
</div>
</div>
<div
v-if="isOtherUser"
v-if="loggedIn && isOtherUser"
class="user-interactions"
>
<div
v-if="loggedIn"
class="follow"
>
<span v-if="user.following">
<!--Following them!-->
<button
class="pressed"
:disabled="followRequestInProgress"
:title="$t('user_card.follow_unfollow')"
@click="unfollowUser"
>
<template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else>
{{ $t('user_card.following') }}
</template>
</button>
</span>
<span v-if="!user.following">
<button
:disabled="followRequestInProgress"
:title="followRequestSent ? $t('user_card.follow_again') : ''"
@click="followUser"
>
<template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else-if="followRequestSent">
{{ $t('user_card.follow_sent') }}
</template>
<template v-else>
{{ $t('user_card.follow') }}
</template>
</button>
</span>
<div v-if="!user.following">
<button
class="btn btn-default btn-block"
:disabled="followRequestInProgress"
:title="followRequestSent ? $t('user_card.follow_again') : ''"
@click="followUser"
>
<template v-if="followRequestInProgress">
{{ $t('user_card.follow_progress') }}
</template>
<template v-else-if="followRequestSent">
{{ $t('user_card.follow_sent') }}
</template>
<template v-else>
{{ $t('user_card.follow') }}
</template>
</button>
</div>
<div v-else-if="followRequestInProgress">
<button
class="btn btn-default btn-block pressed"
disabled
:title="$t('user_card.follow_unfollow')"
@click="unfollowUser"
>
{{ $t('user_card.follow_progress') }}
</button>
</div>
<div
v-if="isOtherUser && loggedIn"
class="mute"
v-else
class="btn-group"
>
<span v-if="user.muted">
<button
class="pressed"
@click="unmuteUser"
>
{{ $t('user_card.muted') }}
</button>
</span>
<span v-if="!user.muted">
<button @click="muteUser">
{{ $t('user_card.mute') }}
</button>
</span>
<button
class="btn btn-default pressed"
:title="$t('user_card.follow_unfollow')"
@click="unfollowUser"
>
{{ $t('user_card.following') }}
</button>
<ProgressButton
v-if="!user.subscribed"
class="btn btn-default"
:click="subscribeUser"
:title="$t('user_card.subscribe')"
>
<i class="icon-bell-alt" />
</ProgressButton>
<ProgressButton
v-else
class="btn btn-default pressed"
:click="unsubscribeUser"
:title="$t('user_card.unsubscribe')"
>
<i class="icon-bell-ringing-o" />
</ProgressButton>
</div>
<div v-if="!loggedIn && user.is_local">
<RemoteFollow :user="user" />
<div>
<button
v-if="user.muted"
class="btn btn-default btn-block pressed"
@click="unmuteUser"
>
{{ $t('user_card.muted') }}
</button>
<button
v-else
class="btn btn-default btn-block"
@click="muteUser"
>
{{ $t('user_card.mute') }}
</button>
</div>
<div
v-if="isOtherUser && loggedIn"
class="block"
>
<span v-if="user.statusnet_blocking">
<button
class="pressed"
@click="unblockUser"
>
{{ $t('user_card.blocked') }}
</button>
</span>
<span v-if="!user.statusnet_blocking">
<button @click="blockUser">
{{ $t('user_card.block') }}
</button>
</span>
<div>
<button
v-if="user.statusnet_blocking"
class="btn btn-default btn-block pressed"
@click="unblockUser"
>
{{ $t('user_card.blocked') }}
</button>
<button
v-else
class="btn btn-default btn-block"
@click="blockUser"
>
{{ $t('user_card.block') }}
</button>
</div>
<div
v-if="isOtherUser && loggedIn"
class="block"
>
<span>
<button @click="reportUser">
{{ $t('user_card.report') }}
</button>
</span>
<div>
<button
class="btn btn-default btn-block"
@click="reportUser"
>
{{ $t('user_card.report') }}
</button>
</div>
<ModerationTools
v-if="loggedIn.role === &quot;admin&quot;"
:user="user"
/>
</div>
<div
v-if="!loggedIn && user.is_local"
class="user-interactions"
>
<RemoteFollow :user="user" />
</div>
</div>
</div>
<div
@ -487,40 +506,22 @@
display: flex;
flex-flow: row wrap;
justify-content: space-between;
margin-right: -.75em;
div {
> * {
flex: 1 0 0;
margin-right: .75em;
margin-bottom: .6em;
margin: 0 .75em .6em 0;
white-space: nowrap;
}
.mute {
max-width: 220px;
min-height: 28px;
}
.follow {
max-width: 220px;
min-height: 28px;
}
button {
width: 100%;
height: 100%;
margin: 0;
}
.remote-button {
height: 28px !important;
width: 92%;
}
.pressed {
border-bottom-color: rgba(255, 255, 255, 0.2);
border-top-color: rgba(0, 0, 0, 0.2);
&.pressed {
// TODO: This should be themed.
border-bottom-color: rgba(255, 255, 255, 0.2);
border-top-color: rgba(0, 0, 0, 0.2);
}
}
}
}

View file

@ -3,7 +3,6 @@ import UserCard from '../user_card/user_card.vue'
import FollowCard from '../follow_card/follow_card.vue'
import Timeline from '../timeline/timeline.vue'
import Conversation from '../conversation/conversation.vue'
import ModerationTools from '../moderation_tools/moderation_tools.vue'
import List from '../list/list.vue'
import withLoadMore from '../../hocs/with_load_more/with_load_more'
@ -132,7 +131,6 @@ const UserProfile = {
Timeline,
FollowerList,
FriendList,
ModerationTools,
FollowCard,
Conversation
}

View file

@ -529,6 +529,8 @@
"remote_follow": "Remote follow",
"report": "Report",
"statuses": "Statuses",
"subscribe": "Subscribe",
"unsubscribe": "Unsubscribe",
"unblock": "Unblock",
"unblock_progress": "Unblocking...",
"block_progress": "Blocking...",

View file

@ -135,6 +135,7 @@ export const mutations = {
user.following = relationship.following
user.muted = relationship.muting
user.statusnet_blocking = relationship.blocking
user.subscribed = relationship.subscribing
}
})
},
@ -304,6 +305,14 @@ const users = {
clearFollowers ({ commit }, userId) {
commit('clearFollowers', userId)
},
subscribeUser ({ rootState, commit }, id) {
return rootState.api.backendInteractor.subscribeUser(id)
.then((relationship) => commit('updateUserRelationship', [relationship]))
},
unsubscribeUser ({ rootState, commit }, id) {
return rootState.api.backendInteractor.unsubscribeUser(id)
.then((relationship) => commit('updateUserRelationship', [relationship]))
},
registerPushNotifications (store) {
const token = store.state.currentUser.credentials
const vapidPublicKey = store.rootState.instance.vapidPublicKey

View file

@ -55,6 +55,8 @@ const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block`
const MASTODON_UNBLOCK_USER_URL = id => `/api/v1/accounts/${id}/unblock`
const MASTODON_MUTE_USER_URL = id => `/api/v1/accounts/${id}/mute`
const MASTODON_UNMUTE_USER_URL = id => `/api/v1/accounts/${id}/unmute`
const MASTODON_SUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/subscribe`
const MASTODON_UNSUBSCRIBE_USER = id => `/api/v1/pleroma/accounts/${id}/unsubscribe`
const MASTODON_POST_STATUS_URL = '/api/v1/statuses'
const MASTODON_MEDIA_UPLOAD_URL = '/api/v1/media'
const MASTODON_VOTE_URL = id => `/api/v1/polls/${id}/votes`
@ -752,6 +754,14 @@ const unmuteUser = ({ id, credentials }) => {
return promisedRequest({ url: MASTODON_UNMUTE_USER_URL(id), credentials, method: 'POST' })
}
const subscribeUser = ({ id, credentials }) => {
return promisedRequest({ url: MASTODON_SUBSCRIBE_USER(id), credentials, method: 'POST' })
}
const unsubscribeUser = ({ id, credentials }) => {
return promisedRequest({ url: MASTODON_UNSUBSCRIBE_USER(id), credentials, method: 'POST' })
}
const fetchBlocks = ({ credentials }) => {
return promisedRequest({ url: MASTODON_USER_BLOCKS_URL, credentials })
.then((users) => users.map(parseUser))
@ -882,6 +892,8 @@ const apiService = {
fetchMutes,
muteUser,
unmuteUser,
subscribeUser,
unsubscribeUser,
fetchBlocks,
fetchOAuthTokens,
revokeOAuthToken,

View file

@ -108,6 +108,8 @@ const backendInteractorService = credentials => {
const fetchMutes = () => apiService.fetchMutes({ credentials })
const muteUser = (id) => apiService.muteUser({ credentials, id })
const unmuteUser = (id) => apiService.unmuteUser({ credentials, id })
const subscribeUser = (id) => apiService.subscribeUser({ credentials, id })
const unsubscribeUser = (id) => apiService.unsubscribeUser({ credentials, id })
const fetchBlocks = () => apiService.fetchBlocks({ credentials })
const fetchFollowRequests = () => apiService.fetchFollowRequests({ credentials })
const fetchOAuthTokens = () => apiService.fetchOAuthTokens({ credentials })
@ -167,6 +169,8 @@ const backendInteractorService = credentials => {
fetchMutes,
muteUser,
unmuteUser,
subscribeUser,
unsubscribeUser,
fetchBlocks,
fetchOAuthTokens,
revokeOAuthToken,

View file

@ -68,6 +68,7 @@ export const parseUser = (data) => {
output.following = relationship.following
output.statusnet_blocking = relationship.blocking
output.muted = relationship.muting
output.subscribed = relationship.subscribing
}
output.hide_follows = data.pleroma.hide_follows

0
static/font/LICENSE.txt Normal file → Executable file
View file

0
static/font/README.txt Normal file → Executable file
View file

20
static/font/config.json Normal file → Executable file
View file

@ -150,12 +150,6 @@
"code": 61669,
"src": "fontawesome"
},
{
"uid": "cd21cbfb28ad4d903cede582157f65dc",
"css": "bell",
"code": 59408,
"src": "fontawesome"
},
{
"uid": "ccc2329632396dc096bb638d4b46fb98",
"css": "mail-alt",
@ -277,6 +271,20 @@
"search": [
"ellipsis"
]
},
{
"uid": "0bef873af785ead27781fdf98b3ae740",
"css": "bell-ringing-o",
"code": 59408,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M497.8 0C468.3 0 444.4 23.9 444.4 53.3 444.4 61.1 446.1 68.3 448.9 75 301.7 96.7 213.3 213.3 213.3 320 213.3 588.3 117.8 712.8 35.6 782.2 35.6 821.1 67.8 853.3 106.7 853.3H355.6C355.6 931.7 419.4 995.6 497.8 995.6S640 931.7 640 853.3H888.9C927.8 853.3 960 821.1 960 782.2 877.8 712.8 782.2 588.3 782.2 320 782.2 213.3 693.9 96.7 546.7 75 549.4 68.3 551.1 61.1 551.1 53.3 551.1 23.9 527.2 0 497.8 0ZM189.4 44.8C108.4 118.6 70.5 215.1 71.1 320.2L142.2 319.8C141.7 231.2 170.4 158.3 237.3 97.4L189.4 44.8ZM806.2 44.8L758.3 97.4C825.2 158.3 853.9 231.2 853.3 319.8L924.4 320.2C925.1 215.1 887.2 118.6 806.2 44.8ZM408.9 844.4C413.9 844.4 417.8 848.3 417.8 853.3 417.8 897.2 453.9 933.3 497.8 933.3 502.8 933.3 506.7 937.2 506.7 942.2S502.8 951.1 497.8 951.1C443.9 951.1 400 907.2 400 853.3 400 848.3 403.9 844.4 408.9 844.4Z",
"width": 1000
},
"search": [
"bell-ringing-o"
]
}
]
}

View file

@ -15,7 +15,7 @@
.icon-right-open:before { content: '\e80d'; } /* '' */
.icon-left-open:before { content: '\e80e'; } /* '' */
.icon-up-open:before { content: '\e80f'; } /* '' */
.icon-bell:before { content: '\e810'; } /* '' */
.icon-bell-ringing-o:before { content: '\e810'; } /* '' */
.icon-lock:before { content: '\e811'; } /* '' */
.icon-globe:before { content: '\e812'; } /* '' */
.icon-brush:before { content: '\e813'; } /* '' */

File diff suppressed because one or more lines are too long

View file

@ -15,7 +15,7 @@
.icon-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
.icon-left-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
.icon-up-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }
.icon-bell { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
.icon-bell-ringing-o { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
.icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); }
.icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); }
.icon-brush { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); }

View file

@ -26,7 +26,7 @@
.icon-right-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
.icon-left-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
.icon-up-open { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }
.icon-bell { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
.icon-bell-ringing-o { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe810;&nbsp;'); }
.icon-lock { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe811;&nbsp;'); }
.icon-globe { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe812;&nbsp;'); }
.icon-brush { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe813;&nbsp;'); }

View file

@ -1,11 +1,11 @@
@font-face {
font-family: 'fontello';
src: url('../font/fontello.eot?3304725');
src: url('../font/fontello.eot?3304725#iefix') format('embedded-opentype'),
url('../font/fontello.woff2?3304725') format('woff2'),
url('../font/fontello.woff?3304725') format('woff'),
url('../font/fontello.ttf?3304725') format('truetype'),
url('../font/fontello.svg?3304725#fontello') format('svg');
src: url('../font/fontello.eot?91349539');
src: url('../font/fontello.eot?91349539#iefix') format('embedded-opentype'),
url('../font/fontello.woff2?91349539') format('woff2'),
url('../font/fontello.woff?91349539') format('woff'),
url('../font/fontello.ttf?91349539') format('truetype'),
url('../font/fontello.svg?91349539#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
@ -15,7 +15,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'fontello';
src: url('../font/fontello.svg?3304725#fontello') format('svg');
src: url('../font/fontello.svg?91349539#fontello') format('svg');
}
}
*/
@ -71,7 +71,7 @@
.icon-right-open:before { content: '\e80d'; } /* '' */
.icon-left-open:before { content: '\e80e'; } /* '' */
.icon-up-open:before { content: '\e80f'; } /* '' */
.icon-bell:before { content: '\e810'; } /* '' */
.icon-bell-ringing-o:before { content: '\e810'; } /* '' */
.icon-lock:before { content: '\e811'; } /* '' */
.icon-globe:before { content: '\e812'; } /* '' */
.icon-brush:before { content: '\e813'; } /* '' */

12
static/font/demo.html Normal file → Executable file
View file

@ -229,11 +229,11 @@ body {
}
@font-face {
font-family: 'fontello';
src: url('./font/fontello.eot?14310629');
src: url('./font/fontello.eot?14310629#iefix') format('embedded-opentype'),
url('./font/fontello.woff?14310629') format('woff'),
url('./font/fontello.ttf?14310629') format('truetype'),
url('./font/fontello.svg?14310629#fontello') format('svg');
src: url('./font/fontello.eot?82370835');
src: url('./font/fontello.eot?82370835#iefix') format('embedded-opentype'),
url('./font/fontello.woff?82370835') format('woff'),
url('./font/fontello.ttf?82370835') format('truetype'),
url('./font/fontello.svg?82370835#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
@ -322,7 +322,7 @@ body {
<div class="the-icons span3" title="Code: 0xe80f"><i class="demo-icon icon-up-open">&#xe80f;</i> <span class="i-name">icon-up-open</span><span class="i-code">0xe80f</span></div>
</div>
<div class="row">
<div class="the-icons span3" title="Code: 0xe810"><i class="demo-icon icon-bell">&#xe810;</i> <span class="i-name">icon-bell</span><span class="i-code">0xe810</span></div>
<div class="the-icons span3" title="Code: 0xe810"><i class="demo-icon icon-bell-ringing-o">&#xe810;</i> <span class="i-name">icon-bell-ringing-o</span><span class="i-code">0xe810</span></div>
<div class="the-icons span3" title="Code: 0xe811"><i class="demo-icon icon-lock">&#xe811;</i> <span class="i-name">icon-lock</span><span class="i-code">0xe811</span></div>
<div class="the-icons span3" title="Code: 0xe812"><i class="demo-icon icon-globe">&#xe812;</i> <span class="i-name">icon-globe</span><span class="i-code">0xe812</span></div>
<div class="the-icons span3" title="Code: 0xe813"><i class="demo-icon icon-brush">&#xe813;</i> <span class="i-name">icon-brush</span><span class="i-code">0xe813</span></div>

Binary file not shown.

View file

@ -38,7 +38,7 @@
<glyph glyph-name="up-open" unicode="&#xe80f;" d="M939 114l-92-92q-11-10-26-10t-25 10l-296 297-296-297q-11-10-25-10t-25 10l-93 92q-11 11-11 26t11 25l414 414q11 10 25 10t25-10l414-414q11-11 11-25t-11-26z" horiz-adv-x="1000" />
<glyph glyph-name="bell" unicode="&#xe810;" d="M509-89q0 8-9 8-33 0-57 24t-23 57q0 9-9 9t-9-9q0-41 29-70t69-28q9 0 9 9z m-372 160h726q-149 168-149 465 0 28-13 58t-39 58-67 45-95 17-95-17-67-45-39-58-13-58q0-297-149-465z m827 0q0-29-21-50t-50-21h-250q0-59-42-101t-101-42-101 42-42 101h-250q-29 0-50 21t-21 50q28 24 51 49t47 67 42 89 27 115 11 145q0 84 66 157t171 89q-5 10-5 21 0 23 16 38t38 16 38-16 16-38q0-11-5-21 106-16 171-89t66-157q0-78 11-145t28-115 41-89 48-67 50-49z" horiz-adv-x="1000" />
<glyph glyph-name="bell-ringing-o" unicode="&#xe810;" d="M498 857c-30 0-54-24-54-53 0-8 2-15 5-22-147-22-236-138-236-245 0-268-95-393-177-462 0-39 32-71 71-71h249c0-79 63-143 142-143s142 64 142 143h249c39 0 71 32 71 71-82 69-178 194-178 462 0 107-88 223-235 245 2 7 4 14 4 22 0 29-24 53-53 53z m-309-45c-81-74-118-170-118-275l71 0c0 89 28 162 95 223l-48 52z m617 0l-48-52c67-61 96-134 95-223l71 0c1 105-37 201-118 275z m-397-799c5 0 9-4 9-9 0-44 36-80 80-80 5 0 9-4 9-9s-4-9-9-9c-54 0-98 44-98 98 0 5 4 9 9 9z" horiz-adv-x="1000" />
<glyph glyph-name="lock" unicode="&#xe811;" d="M179 428h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.