Report UI (end-user)
This commit is contained in:
parent
1a8edf27b3
commit
33d1f879cf
|
@ -1,6 +1,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from funkwhale_api.activity import serializers as activity_serializers
|
from funkwhale_api.activity import serializers as activity_serializers
|
||||||
|
from funkwhale_api.federation import serializers as federation_serializers
|
||||||
from funkwhale_api.music.serializers import TrackActivitySerializer, TrackSerializer
|
from funkwhale_api.music.serializers import TrackActivitySerializer, TrackSerializer
|
||||||
from funkwhale_api.users.serializers import UserActivitySerializer, UserBasicSerializer
|
from funkwhale_api.users.serializers import UserActivitySerializer, UserBasicSerializer
|
||||||
|
|
||||||
|
@ -27,10 +28,17 @@ class TrackFavoriteActivitySerializer(activity_serializers.ModelSerializer):
|
||||||
class UserTrackFavoriteSerializer(serializers.ModelSerializer):
|
class UserTrackFavoriteSerializer(serializers.ModelSerializer):
|
||||||
track = TrackSerializer(read_only=True)
|
track = TrackSerializer(read_only=True)
|
||||||
user = UserBasicSerializer(read_only=True)
|
user = UserBasicSerializer(read_only=True)
|
||||||
|
actor = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.TrackFavorite
|
model = models.TrackFavorite
|
||||||
fields = ("id", "user", "track", "creation_date")
|
fields = ("id", "user", "track", "creation_date", "actor")
|
||||||
|
actor = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
def get_actor(self, obj):
|
||||||
|
actor = obj.user.actor
|
||||||
|
if actor:
|
||||||
|
return federation_serializers.APIActorSerializer(actor).data
|
||||||
|
|
||||||
|
|
||||||
class UserTrackFavoriteWriteSerializer(serializers.ModelSerializer):
|
class UserTrackFavoriteWriteSerializer(serializers.ModelSerializer):
|
||||||
|
|
|
@ -22,7 +22,7 @@ class TrackFavoriteViewSet(
|
||||||
|
|
||||||
filterset_class = filters.TrackFavoriteFilter
|
filterset_class = filters.TrackFavoriteFilter
|
||||||
serializer_class = serializers.UserTrackFavoriteSerializer
|
serializer_class = serializers.UserTrackFavoriteSerializer
|
||||||
queryset = models.TrackFavorite.objects.all().select_related("user")
|
queryset = models.TrackFavorite.objects.all().select_related("user__actor")
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
oauth_permissions.ScopePermission,
|
oauth_permissions.ScopePermission,
|
||||||
permissions.OwnerPermission,
|
permissions.OwnerPermission,
|
||||||
|
@ -54,7 +54,7 @@ class TrackFavoriteViewSet(
|
||||||
)
|
)
|
||||||
tracks = Track.objects.with_playable_uploads(
|
tracks = Track.objects.with_playable_uploads(
|
||||||
music_utils.get_actor_from_request(self.request)
|
music_utils.get_actor_from_request(self.request)
|
||||||
).select_related("artist", "album__artist")
|
).select_related("artist", "album__artist", "attributed_to")
|
||||||
queryset = queryset.prefetch_related(Prefetch("track", queryset=tracks))
|
queryset = queryset.prefetch_related(Prefetch("track", queryset=tracks))
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from funkwhale_api.activity import serializers as activity_serializers
|
from funkwhale_api.activity import serializers as activity_serializers
|
||||||
|
from funkwhale_api.federation import serializers as federation_serializers
|
||||||
from funkwhale_api.music.serializers import TrackActivitySerializer, TrackSerializer
|
from funkwhale_api.music.serializers import TrackActivitySerializer, TrackSerializer
|
||||||
from funkwhale_api.users.serializers import UserActivitySerializer, UserBasicSerializer
|
from funkwhale_api.users.serializers import UserActivitySerializer, UserBasicSerializer
|
||||||
|
|
||||||
|
@ -27,16 +28,22 @@ class ListeningActivitySerializer(activity_serializers.ModelSerializer):
|
||||||
class ListeningSerializer(serializers.ModelSerializer):
|
class ListeningSerializer(serializers.ModelSerializer):
|
||||||
track = TrackSerializer(read_only=True)
|
track = TrackSerializer(read_only=True)
|
||||||
user = UserBasicSerializer(read_only=True)
|
user = UserBasicSerializer(read_only=True)
|
||||||
|
actor = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Listening
|
model = models.Listening
|
||||||
fields = ("id", "user", "track", "creation_date")
|
fields = ("id", "user", "track", "creation_date", "actor")
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
validated_data["user"] = self.context["user"]
|
validated_data["user"] = self.context["user"]
|
||||||
|
|
||||||
return super().create(validated_data)
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
def get_actor(self, obj):
|
||||||
|
actor = obj.user.actor
|
||||||
|
if actor:
|
||||||
|
return federation_serializers.APIActorSerializer(actor).data
|
||||||
|
|
||||||
|
|
||||||
class ListeningWriteSerializer(serializers.ModelSerializer):
|
class ListeningWriteSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -19,7 +19,7 @@ class ListeningViewSet(
|
||||||
):
|
):
|
||||||
|
|
||||||
serializer_class = serializers.ListeningSerializer
|
serializer_class = serializers.ListeningSerializer
|
||||||
queryset = models.Listening.objects.all().select_related("user")
|
queryset = models.Listening.objects.all().select_related("user__actor")
|
||||||
|
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
oauth_permissions.ScopePermission,
|
oauth_permissions.ScopePermission,
|
||||||
|
@ -47,7 +47,7 @@ class ListeningViewSet(
|
||||||
)
|
)
|
||||||
tracks = Track.objects.with_playable_uploads(
|
tracks = Track.objects.with_playable_uploads(
|
||||||
music_utils.get_actor_from_request(self.request)
|
music_utils.get_actor_from_request(self.request)
|
||||||
).select_related("artist", "album__artist")
|
).select_related("artist", "album__artist", "attributed_to")
|
||||||
return queryset.prefetch_related(Prefetch("track", queryset=tracks))
|
return queryset.prefetch_related(Prefetch("track", queryset=tracks))
|
||||||
|
|
||||||
def get_serializer_context(self):
|
def get_serializer_context(self):
|
||||||
|
|
|
@ -3,6 +3,7 @@ import memoize.djangocache
|
||||||
import funkwhale_api
|
import funkwhale_api
|
||||||
from funkwhale_api.common import preferences
|
from funkwhale_api.common import preferences
|
||||||
from funkwhale_api.federation import actors, models as federation_models
|
from funkwhale_api.federation import actors, models as federation_models
|
||||||
|
from funkwhale_api.moderation import models as moderation_models
|
||||||
from funkwhale_api.music import utils as music_utils
|
from funkwhale_api.music import utils as music_utils
|
||||||
|
|
||||||
from . import stats
|
from . import stats
|
||||||
|
@ -15,6 +16,9 @@ def get():
|
||||||
share_stats = preferences.get("instance__nodeinfo_stats_enabled")
|
share_stats = preferences.get("instance__nodeinfo_stats_enabled")
|
||||||
allow_list_enabled = preferences.get("moderation__allow_list_enabled")
|
allow_list_enabled = preferences.get("moderation__allow_list_enabled")
|
||||||
allow_list_public = preferences.get("moderation__allow_list_public")
|
allow_list_public = preferences.get("moderation__allow_list_public")
|
||||||
|
unauthenticated_report_types = preferences.get(
|
||||||
|
"moderation__unauthenticated_report_types"
|
||||||
|
)
|
||||||
if allow_list_enabled and allow_list_public:
|
if allow_list_enabled and allow_list_public:
|
||||||
allowed_domains = list(
|
allowed_domains = list(
|
||||||
federation_models.Domain.objects.filter(allowed=True)
|
federation_models.Domain.objects.filter(allowed=True)
|
||||||
|
@ -47,6 +51,10 @@ def get():
|
||||||
},
|
},
|
||||||
"supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS,
|
"supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS,
|
||||||
"allowList": {"enabled": allow_list_enabled, "domains": allowed_domains},
|
"allowList": {"enabled": allow_list_enabled, "domains": allowed_domains},
|
||||||
|
"reportTypes": [
|
||||||
|
{"type": t, "label": l, "anonymous": t in unauthenticated_report_types}
|
||||||
|
for t, l in moderation_models.REPORT_TYPES
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if share_stats:
|
if share_stats:
|
||||||
|
|
|
@ -115,7 +115,7 @@ REPORT_TYPES = [
|
||||||
class Report(federation_models.FederationMixin):
|
class Report(federation_models.FederationMixin):
|
||||||
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
|
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
|
||||||
creation_date = models.DateTimeField(default=timezone.now)
|
creation_date = models.DateTimeField(default=timezone.now)
|
||||||
summary = models.TextField(null=True, max_length=50000)
|
summary = models.TextField(null=True, blank=True, max_length=50000)
|
||||||
handled_date = models.DateTimeField(null=True)
|
handled_date = models.DateTimeField(null=True)
|
||||||
is_handled = models.BooleanField(default=False)
|
is_handled = models.BooleanField(default=False)
|
||||||
type = models.CharField(max_length=40, choices=REPORT_TYPES)
|
type = models.CharField(max_length=40, choices=REPORT_TYPES)
|
||||||
|
|
|
@ -2,6 +2,7 @@ from django.db import transaction
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from funkwhale_api.common import preferences
|
from funkwhale_api.common import preferences
|
||||||
|
from funkwhale_api.federation import serializers as federation_serializers
|
||||||
from funkwhale_api.music.models import Track
|
from funkwhale_api.music.models import Track
|
||||||
from funkwhale_api.music.serializers import TrackSerializer
|
from funkwhale_api.music.serializers import TrackSerializer
|
||||||
from funkwhale_api.users.serializers import UserBasicSerializer
|
from funkwhale_api.users.serializers import UserBasicSerializer
|
||||||
|
@ -79,6 +80,7 @@ class PlaylistSerializer(serializers.ModelSerializer):
|
||||||
album_covers = serializers.SerializerMethodField(read_only=True)
|
album_covers = serializers.SerializerMethodField(read_only=True)
|
||||||
user = UserBasicSerializer(read_only=True)
|
user = UserBasicSerializer(read_only=True)
|
||||||
is_playable = serializers.SerializerMethodField()
|
is_playable = serializers.SerializerMethodField()
|
||||||
|
actor = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Playlist
|
model = models.Playlist
|
||||||
|
@ -93,9 +95,15 @@ class PlaylistSerializer(serializers.ModelSerializer):
|
||||||
"album_covers",
|
"album_covers",
|
||||||
"duration",
|
"duration",
|
||||||
"is_playable",
|
"is_playable",
|
||||||
|
"actor",
|
||||||
)
|
)
|
||||||
read_only_fields = ["id", "modification_date", "creation_date"]
|
read_only_fields = ["id", "modification_date", "creation_date"]
|
||||||
|
|
||||||
|
def get_actor(self, obj):
|
||||||
|
actor = obj.user.actor
|
||||||
|
if actor:
|
||||||
|
return federation_serializers.APIActorSerializer(actor).data
|
||||||
|
|
||||||
def get_is_playable(self, obj):
|
def get_is_playable(self, obj):
|
||||||
try:
|
try:
|
||||||
return bool(obj.playable_plts)
|
return bool(obj.playable_plts)
|
||||||
|
|
|
@ -23,7 +23,7 @@ class PlaylistViewSet(
|
||||||
serializer_class = serializers.PlaylistSerializer
|
serializer_class = serializers.PlaylistSerializer
|
||||||
queryset = (
|
queryset = (
|
||||||
models.Playlist.objects.all()
|
models.Playlist.objects.all()
|
||||||
.select_related("user")
|
.select_related("user__actor")
|
||||||
.annotate(tracks_count=Count("playlist_tracks"))
|
.annotate(tracks_count=Count("playlist_tracks"))
|
||||||
.with_covers()
|
.with_covers()
|
||||||
.with_duration()
|
.with_duration()
|
||||||
|
|
|
@ -4,8 +4,7 @@ import pytest
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from funkwhale_api.favorites.models import TrackFavorite
|
from funkwhale_api.favorites.models import TrackFavorite
|
||||||
from funkwhale_api.music import serializers as music_serializers
|
from funkwhale_api.favorites import serializers
|
||||||
from funkwhale_api.users import serializers as users_serializers
|
|
||||||
|
|
||||||
|
|
||||||
def test_user_can_add_favorite(factories):
|
def test_user_can_add_favorite(factories):
|
||||||
|
@ -20,22 +19,15 @@ def test_user_can_add_favorite(factories):
|
||||||
def test_user_can_get_his_favorites(
|
def test_user_can_get_his_favorites(
|
||||||
api_request, factories, logged_in_api_client, client
|
api_request, factories, logged_in_api_client, client
|
||||||
):
|
):
|
||||||
r = api_request.get("/")
|
request = api_request.get("/")
|
||||||
favorite = factories["favorites.TrackFavorite"](user=logged_in_api_client.user)
|
favorite = factories["favorites.TrackFavorite"](user=logged_in_api_client.user)
|
||||||
factories["favorites.TrackFavorite"]()
|
factories["favorites.TrackFavorite"]()
|
||||||
url = reverse("api:v1:favorites:tracks-list")
|
url = reverse("api:v1:favorites:tracks-list")
|
||||||
response = logged_in_api_client.get(url, {"user": logged_in_api_client.user.pk})
|
response = logged_in_api_client.get(url, {"user": logged_in_api_client.user.pk})
|
||||||
expected = [
|
expected = [
|
||||||
{
|
serializers.UserTrackFavoriteSerializer(
|
||||||
"user": users_serializers.UserBasicSerializer(
|
favorite, context={"request": request}
|
||||||
favorite.user, context={"request": r}
|
).data
|
||||||
).data,
|
|
||||||
"track": music_serializers.TrackSerializer(
|
|
||||||
favorite.track, context={"request": r}
|
|
||||||
).data,
|
|
||||||
"id": favorite.id,
|
|
||||||
"creation_date": favorite.creation_date.isoformat().replace("+00:00", "Z"),
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.data["results"] == expected
|
assert response.data["results"] == expected
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
from funkwhale_api.federation import serializers as federation_serializers
|
||||||
|
from funkwhale_api.favorites import serializers
|
||||||
|
from funkwhale_api.music import serializers as music_serializers
|
||||||
|
from funkwhale_api.users import serializers as users_serializers
|
||||||
|
|
||||||
|
|
||||||
|
def test_track_favorite_serializer(factories, to_api_date):
|
||||||
|
favorite = factories["favorites.TrackFavorite"]()
|
||||||
|
actor = favorite.user.create_actor()
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
"id": favorite.pk,
|
||||||
|
"creation_date": to_api_date(favorite.creation_date),
|
||||||
|
"track": music_serializers.TrackSerializer(favorite.track).data,
|
||||||
|
"actor": federation_serializers.APIActorSerializer(actor).data,
|
||||||
|
"user": users_serializers.UserBasicSerializer(favorite.user).data,
|
||||||
|
}
|
||||||
|
serializer = serializers.UserTrackFavoriteSerializer(favorite)
|
||||||
|
|
||||||
|
assert serializer.data == expected
|
|
@ -0,0 +1,20 @@
|
||||||
|
from funkwhale_api.federation import serializers as federation_serializers
|
||||||
|
from funkwhale_api.history import serializers
|
||||||
|
from funkwhale_api.music import serializers as music_serializers
|
||||||
|
from funkwhale_api.users import serializers as users_serializers
|
||||||
|
|
||||||
|
|
||||||
|
def test_listening_serializer(factories, to_api_date):
|
||||||
|
listening = factories["history.Listening"]()
|
||||||
|
actor = listening.user.create_actor()
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
"id": listening.pk,
|
||||||
|
"creation_date": to_api_date(listening.creation_date),
|
||||||
|
"track": music_serializers.TrackSerializer(listening.track).data,
|
||||||
|
"actor": federation_serializers.APIActorSerializer(actor).data,
|
||||||
|
"user": users_serializers.UserBasicSerializer(listening.user).data,
|
||||||
|
}
|
||||||
|
serializer = serializers.ListeningSerializer(listening)
|
||||||
|
|
||||||
|
assert serializer.data == expected
|
|
@ -8,6 +8,12 @@ from funkwhale_api.music import utils as music_utils
|
||||||
|
|
||||||
def test_nodeinfo_dump(preferences, mocker):
|
def test_nodeinfo_dump(preferences, mocker):
|
||||||
preferences["instance__nodeinfo_stats_enabled"] = True
|
preferences["instance__nodeinfo_stats_enabled"] = True
|
||||||
|
preferences["moderation__unauthenticated_report_types"] = [
|
||||||
|
"takedown_request",
|
||||||
|
"other",
|
||||||
|
"other_category_that_doesnt_exist",
|
||||||
|
]
|
||||||
|
|
||||||
stats = {
|
stats = {
|
||||||
"users": {"total": 1, "active_halfyear": 12, "active_month": 13},
|
"users": {"total": 1, "active_halfyear": 12, "active_month": 13},
|
||||||
"tracks": 2,
|
"tracks": 2,
|
||||||
|
@ -51,6 +57,29 @@ def test_nodeinfo_dump(preferences, mocker):
|
||||||
},
|
},
|
||||||
"supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS,
|
"supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS,
|
||||||
"allowList": {"enabled": False, "domains": None},
|
"allowList": {"enabled": False, "domains": None},
|
||||||
|
"reportTypes": [
|
||||||
|
{
|
||||||
|
"type": "takedown_request",
|
||||||
|
"label": "Takedown request",
|
||||||
|
"anonymous": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "invalid_metadata",
|
||||||
|
"label": "Invalid metadata",
|
||||||
|
"anonymous": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "illegal_content",
|
||||||
|
"label": "Illegal content",
|
||||||
|
"anonymous": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "offensive_content",
|
||||||
|
"label": "Offensive content",
|
||||||
|
"anonymous": False,
|
||||||
|
},
|
||||||
|
{"type": "other", "label": "Other", "anonymous": True},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert nodeinfo.get() == expected
|
assert nodeinfo.get() == expected
|
||||||
|
@ -58,6 +87,10 @@ def test_nodeinfo_dump(preferences, mocker):
|
||||||
|
|
||||||
def test_nodeinfo_dump_stats_disabled(preferences, mocker):
|
def test_nodeinfo_dump_stats_disabled(preferences, mocker):
|
||||||
preferences["instance__nodeinfo_stats_enabled"] = False
|
preferences["instance__nodeinfo_stats_enabled"] = False
|
||||||
|
preferences["moderation__unauthenticated_report_types"] = [
|
||||||
|
"takedown_request",
|
||||||
|
"other",
|
||||||
|
]
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
"version": "2.0",
|
"version": "2.0",
|
||||||
|
@ -83,6 +116,29 @@ def test_nodeinfo_dump_stats_disabled(preferences, mocker):
|
||||||
},
|
},
|
||||||
"supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS,
|
"supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS,
|
||||||
"allowList": {"enabled": False, "domains": None},
|
"allowList": {"enabled": False, "domains": None},
|
||||||
|
"reportTypes": [
|
||||||
|
{
|
||||||
|
"type": "takedown_request",
|
||||||
|
"label": "Takedown request",
|
||||||
|
"anonymous": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "invalid_metadata",
|
||||||
|
"label": "Invalid metadata",
|
||||||
|
"anonymous": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "illegal_content",
|
||||||
|
"label": "Illegal content",
|
||||||
|
"anonymous": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "offensive_content",
|
||||||
|
"label": "Offensive content",
|
||||||
|
"anonymous": False,
|
||||||
|
},
|
||||||
|
{"type": "other", "label": "Other", "anonymous": True},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
assert nodeinfo.get() == expected
|
assert nodeinfo.get() == expected
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
from funkwhale_api.federation import serializers as federation_serializers
|
||||||
from funkwhale_api.playlists import models, serializers
|
from funkwhale_api.playlists import models, serializers
|
||||||
|
from funkwhale_api.users import serializers as users_serializers
|
||||||
|
|
||||||
|
|
||||||
def test_cannot_max_500_tracks_per_playlist(factories, preferences):
|
def test_cannot_max_500_tracks_per_playlist(factories, preferences):
|
||||||
|
@ -124,3 +126,25 @@ def test_playlist_serializer_include_duration(factories, api_request):
|
||||||
|
|
||||||
serializer = serializers.PlaylistSerializer(qs.get())
|
serializer = serializers.PlaylistSerializer(qs.get())
|
||||||
assert serializer.data["duration"] == 45
|
assert serializer.data["duration"] == 45
|
||||||
|
|
||||||
|
|
||||||
|
def test_playlist_serializer(factories, to_api_date):
|
||||||
|
playlist = factories["playlists.Playlist"]()
|
||||||
|
actor = playlist.user.create_actor()
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
"id": playlist.pk,
|
||||||
|
"name": playlist.name,
|
||||||
|
"privacy_level": playlist.privacy_level,
|
||||||
|
"is_playable": None,
|
||||||
|
"creation_date": to_api_date(playlist.creation_date),
|
||||||
|
"modification_date": to_api_date(playlist.modification_date),
|
||||||
|
"actor": federation_serializers.APIActorSerializer(actor).data,
|
||||||
|
"user": users_serializers.UserBasicSerializer(playlist.user).data,
|
||||||
|
"duration": 0,
|
||||||
|
"tracks_count": 0,
|
||||||
|
"album_covers": [],
|
||||||
|
}
|
||||||
|
serializer = serializers.PlaylistSerializer(playlist)
|
||||||
|
|
||||||
|
assert serializer.data == expected
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
></app-footer>
|
></app-footer>
|
||||||
<playlist-modal v-if="$store.state.auth.authenticated"></playlist-modal>
|
<playlist-modal v-if="$store.state.auth.authenticated"></playlist-modal>
|
||||||
<filter-modal v-if="$store.state.auth.authenticated"></filter-modal>
|
<filter-modal v-if="$store.state.auth.authenticated"></filter-modal>
|
||||||
|
<report-modal></report-modal>
|
||||||
<shortcuts-modal @update:show="showShortcutsModal = $event" :show="showShortcutsModal"></shortcuts-modal>
|
<shortcuts-modal @update:show="showShortcutsModal = $event" :show="showShortcutsModal"></shortcuts-modal>
|
||||||
<GlobalEvents @keydown.h.exact="showShortcutsModal = !showShortcutsModal"/>
|
<GlobalEvents @keydown.h.exact="showShortcutsModal = !showShortcutsModal"/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -41,6 +42,7 @@ import moment from 'moment'
|
||||||
import locales from './locales'
|
import locales from './locales'
|
||||||
import PlaylistModal from '@/components/playlists/PlaylistModal'
|
import PlaylistModal from '@/components/playlists/PlaylistModal'
|
||||||
import FilterModal from '@/components/moderation/FilterModal'
|
import FilterModal from '@/components/moderation/FilterModal'
|
||||||
|
import ReportModal from '@/components/moderation/ReportModal'
|
||||||
import ShortcutsModal from '@/components/ShortcutsModal'
|
import ShortcutsModal from '@/components/ShortcutsModal'
|
||||||
import SetInstanceModal from '@/components/SetInstanceModal'
|
import SetInstanceModal from '@/components/SetInstanceModal'
|
||||||
|
|
||||||
|
@ -50,6 +52,7 @@ export default {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
AppFooter,
|
AppFooter,
|
||||||
FilterModal,
|
FilterModal,
|
||||||
|
ReportModal,
|
||||||
PlaylistModal,
|
PlaylistModal,
|
||||||
ShortcutsModal,
|
ShortcutsModal,
|
||||||
GlobalEvents,
|
GlobalEvents,
|
||||||
|
|
|
@ -27,9 +27,17 @@
|
||||||
<button v-if="track" class="item basic" :disabled="!playable" @click.stop.prevent="$store.dispatch('radios/start', {type: 'similar', objectId: track.id})" :title="labels.startRadio">
|
<button v-if="track" class="item basic" :disabled="!playable" @click.stop.prevent="$store.dispatch('radios/start', {type: 'similar', objectId: track.id})" :title="labels.startRadio">
|
||||||
<i class="feed icon"></i><translate translate-context="*/Queue/Button.Label/Short, Verb">Start radio</translate>
|
<i class="feed icon"></i><translate translate-context="*/Queue/Button.Label/Short, Verb">Start radio</translate>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="divider"></div>
|
||||||
<button v-if="filterableArtist" class="item basic" :disabled="!filterableArtist" @click.stop.prevent="filterArtist" :title="labels.hideArtist">
|
<button v-if="filterableArtist" class="item basic" :disabled="!filterableArtist" @click.stop.prevent="filterArtist" :title="labels.hideArtist">
|
||||||
<i class="eye slash outline icon"></i><translate translate-context="*/Queue/Dropdown/Button/Label/Short">Hide content from this artist</translate>
|
<i class="eye slash outline icon"></i><translate translate-context="*/Queue/Dropdown/Button/Label/Short">Hide content from this artist</translate>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-for="obj in getReportableObjs({track, album, artist, playlist, account})"
|
||||||
|
:key="obj.target.type + obj.target.id"
|
||||||
|
class="item basic"
|
||||||
|
@click.stop.prevent="$store.dispatch('moderation/report', obj.target)">
|
||||||
|
<i class="share icon" /> {{ obj.label }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
@ -39,11 +47,15 @@
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import jQuery from 'jquery'
|
import jQuery from 'jquery'
|
||||||
|
|
||||||
|
import ReportMixin from '@/components/mixins/Report'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [ReportMixin],
|
||||||
props: {
|
props: {
|
||||||
// we can either have a single or multiple tracks to play when clicked
|
// we can either have a single or multiple tracks to play when clicked
|
||||||
tracks: {type: Array, required: false},
|
tracks: {type: Array, required: false},
|
||||||
track: {type: Object, required: false},
|
track: {type: Object, required: false},
|
||||||
|
account: {type: Object, required: false},
|
||||||
dropdownIconClasses: {type: Array, required: false, default: () => { return ['dropdown'] }},
|
dropdownIconClasses: {type: Array, required: false, default: () => { return ['dropdown'] }},
|
||||||
playIconClass: {type: String, required: false, default: 'play icon'},
|
playIconClass: {type: String, required: false, default: 'play icon'},
|
||||||
buttonClasses: {type: Array, required: false, default: () => { return ['button'] }},
|
buttonClasses: {type: Array, required: false, default: () => { return ['button'] }},
|
||||||
|
@ -79,7 +91,8 @@ export default {
|
||||||
addToQueue: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Add to current queue'),
|
addToQueue: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Add to current queue'),
|
||||||
playNext: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play next'),
|
playNext: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play next'),
|
||||||
startRadio: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play similar songs'),
|
startRadio: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play similar songs'),
|
||||||
replacePlay: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Replace current queue')
|
replacePlay: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Replace current queue'),
|
||||||
|
report: this.$pgettext('*/Moderation/*/Button/Label,Verb', 'Report…'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title () {
|
title () {
|
||||||
|
@ -118,7 +131,7 @@ export default {
|
||||||
if (this.artist) {
|
if (this.artist) {
|
||||||
return this.artist
|
return this.artist
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="one wide stretched column">
|
<div class="one wide stretched column">
|
||||||
<play-button class="basic icon" :dropdown-only="true" :dropdown-icon-classes="['ellipsis', 'vertical', 'large', 'grey']" :track="object.track"></play-button>
|
<play-button
|
||||||
|
class="basic icon"
|
||||||
|
:account="object.actor"
|
||||||
|
:dropdown-only="true"
|
||||||
|
:dropdown-icon-classes="['ellipsis', 'vertical', 'large', 'grey']"
|
||||||
|
:track="object.track"></play-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -74,6 +74,15 @@
|
||||||
<translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
|
<translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
class="basic item"
|
||||||
|
v-for="obj in getReportableObjs({album: object})"
|
||||||
|
:key="obj.target.type + obj.target.id"
|
||||||
|
@click.stop.prevent="$store.dispatch('moderation/report', obj.target)">
|
||||||
|
<i class="share icon" /> {{ obj.label }}
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
<router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.albums.detail', params: {id: object.id}}">
|
<router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.albums.detail', params: {id: object.id}}">
|
||||||
<i class="wrench icon"></i>
|
<i class="wrench icon"></i>
|
||||||
<translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
|
<translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
|
||||||
|
@ -105,6 +114,7 @@ import PlayButton from "@/components/audio/PlayButton"
|
||||||
import EmbedWizard from "@/components/audio/EmbedWizard"
|
import EmbedWizard from "@/components/audio/EmbedWizard"
|
||||||
import Modal from '@/components/semantic/Modal'
|
import Modal from '@/components/semantic/Modal'
|
||||||
import TagsList from "@/components/tags/List"
|
import TagsList from "@/components/tags/List"
|
||||||
|
import ReportMixin from '@/components/mixins/Report'
|
||||||
|
|
||||||
const FETCH_URL = "albums/"
|
const FETCH_URL = "albums/"
|
||||||
|
|
||||||
|
@ -121,6 +131,7 @@ function groupByDisc(acc, track) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [ReportMixin],
|
||||||
props: ["id"],
|
props: ["id"],
|
||||||
components: {
|
components: {
|
||||||
PlayButton,
|
PlayButton,
|
||||||
|
|
|
@ -84,6 +84,16 @@
|
||||||
<i class="edit icon"></i>
|
<i class="edit icon"></i>
|
||||||
<translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
|
<translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
class="basic item"
|
||||||
|
v-for="obj in getReportableObjs({artist: object})"
|
||||||
|
:key="obj.target.type + obj.target.id"
|
||||||
|
@click.stop.prevent="$store.dispatch('moderation/report', obj.target)">
|
||||||
|
<i class="share icon" /> {{ obj.label }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.artists.detail', params: {id: object.id}}">
|
<router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.artists.detail', params: {id: object.id}}">
|
||||||
<i class="wrench icon"></i>
|
<i class="wrench icon"></i>
|
||||||
|
@ -125,12 +135,12 @@ import EmbedWizard from "@/components/audio/EmbedWizard"
|
||||||
import Modal from '@/components/semantic/Modal'
|
import Modal from '@/components/semantic/Modal'
|
||||||
import RadioButton from "@/components/radios/Button"
|
import RadioButton from "@/components/radios/Button"
|
||||||
import TagsList from "@/components/tags/List"
|
import TagsList from "@/components/tags/List"
|
||||||
|
import ReportMixin from '@/components/mixins/Report'
|
||||||
|
|
||||||
const FETCH_URL = "albums/"
|
const FETCH_URL = "albums/"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
mixins: [ReportMixin],
|
||||||
props: ["id"],
|
props: ["id"],
|
||||||
components: {
|
components: {
|
||||||
PlayButton,
|
PlayButton,
|
||||||
|
|
|
@ -90,6 +90,15 @@
|
||||||
<translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
|
<translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
class="basic item"
|
||||||
|
v-for="obj in getReportableObjs({track})"
|
||||||
|
:key="obj.target.type + obj.target.id"
|
||||||
|
@click.stop.prevent="$store.dispatch('moderation/report', obj.target)">
|
||||||
|
<i class="share icon" /> {{ obj.label }}
|
||||||
|
</div>
|
||||||
|
<div class="divider"></div>
|
||||||
<router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.tracks.detail', params: {id: track.id}}">
|
<router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.tracks.detail', params: {id: track.id}}">
|
||||||
<i class="wrench icon"></i>
|
<i class="wrench icon"></i>
|
||||||
<translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
|
<translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
|
||||||
|
@ -124,11 +133,13 @@ import TrackPlaylistIcon from "@/components/playlists/TrackPlaylistIcon"
|
||||||
import Modal from '@/components/semantic/Modal'
|
import Modal from '@/components/semantic/Modal'
|
||||||
import EmbedWizard from "@/components/audio/EmbedWizard"
|
import EmbedWizard from "@/components/audio/EmbedWizard"
|
||||||
import TagsList from "@/components/tags/List"
|
import TagsList from "@/components/tags/List"
|
||||||
|
import ReportMixin from '@/components/mixins/Report'
|
||||||
|
|
||||||
const FETCH_URL = "tracks/"
|
const FETCH_URL = "tracks/"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ["id"],
|
props: ["id"],
|
||||||
|
mixins: [ReportMixin],
|
||||||
components: {
|
components: {
|
||||||
PlayButton,
|
PlayButton,
|
||||||
TrackPlaylistIcon,
|
TrackPlaylistIcon,
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
getReportableObjs ({track, album, artist, playlist, account}) {
|
||||||
|
let reportableObjs = []
|
||||||
|
if (account) {
|
||||||
|
let accountLabel = this.$pgettext('*/Moderation/*/Verb', "Report @%{ username }…")
|
||||||
|
reportableObjs.push({
|
||||||
|
label: this.$gettextInterpolate(accountLabel, {username: account.preferred_username}),
|
||||||
|
target: {
|
||||||
|
type: 'account',
|
||||||
|
full_username: account.full_username,
|
||||||
|
label: account.full_username,
|
||||||
|
typeLabel: this.$pgettext("*/*/*", 'Account'),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (track) {
|
||||||
|
album = track.album
|
||||||
|
artist = track.artist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (track) {
|
||||||
|
reportableObjs.push({
|
||||||
|
label: this.$pgettext('*/Moderation/*/Verb', "Report this track…"),
|
||||||
|
target: {
|
||||||
|
type: 'track',
|
||||||
|
id: track.id,
|
||||||
|
label: track.title,
|
||||||
|
typeLabel: this.$pgettext("*/*/*", 'Track'),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
album = track.album
|
||||||
|
artist = track.artist
|
||||||
|
}
|
||||||
|
if (album) {
|
||||||
|
reportableObjs.push({
|
||||||
|
label: this.$pgettext('*/Moderation/*/Verb', "Report this album…"),
|
||||||
|
target: {
|
||||||
|
type: 'album',
|
||||||
|
id: album.id,
|
||||||
|
label: album.title,
|
||||||
|
typeLabel: this.$pgettext("*/*/*", 'Album'),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!artist) {
|
||||||
|
artist = album.artist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (artist) {
|
||||||
|
reportableObjs.push({
|
||||||
|
label: this.$pgettext('*/Moderation/*/Verb', "Report this artist…"),
|
||||||
|
target: {
|
||||||
|
type: 'artist',
|
||||||
|
id: artist.id,
|
||||||
|
label: artist.name,
|
||||||
|
typeLabel: this.$pgettext("*/*/*", 'Artist'),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (this.playlist) {
|
||||||
|
reportableObjs.push({
|
||||||
|
label: this.$pgettext('*/Moderation/*/Verb', "Report this playlist…"),
|
||||||
|
target: {
|
||||||
|
type: 'playlist',
|
||||||
|
id: this.playlist.id,
|
||||||
|
label: this.playlist.name,
|
||||||
|
typeLabel: this.$pgettext("*/*/*", 'Playlist'),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return reportableObjs
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,7 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<label v-if="label"><translate translate-context="*/*/*">Category</translate></label>
|
<label v-if="label"><translate translate-context="*/*/*">Category</translate></label>
|
||||||
<select class="ui dropdown" :value="value" @change="$emit('input', $event.target.value)">
|
<select class="ui dropdown" :value="value" @change="$emit('input', $event.target.value)" :required="required">
|
||||||
|
<option v-if="empty" disabled value=''></option>
|
||||||
<option :value="option.value" v-for="option in allCategories">{{ option.label }}</option>
|
<option :value="option.value" v-for="option in allCategories">{{ option.label }}</option>
|
||||||
</select>
|
</select>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
|
@ -13,7 +14,14 @@ import TranslationsMixin from '@/components/mixins/Translations'
|
||||||
import lodash from '@/lodash'
|
import lodash from '@/lodash'
|
||||||
export default {
|
export default {
|
||||||
mixins: [TranslationsMixin],
|
mixins: [TranslationsMixin],
|
||||||
props: ['value', 'all', 'label'],
|
props: {
|
||||||
|
value: {},
|
||||||
|
all: {},
|
||||||
|
label: {},
|
||||||
|
empty: {},
|
||||||
|
required: {},
|
||||||
|
restrictTo: {default: () => { return [] }}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
allCategories () {
|
allCategories () {
|
||||||
let c = []
|
let c = []
|
||||||
|
@ -25,11 +33,17 @@ export default {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
let choices
|
||||||
|
if (this.restrictTo.length > 0) {
|
||||||
|
choices = this.restrictTo
|
||||||
|
} else {
|
||||||
|
choices = lodash.keys(this.sharedLabels.fields.report_type.choices)
|
||||||
|
}
|
||||||
return c.concat(
|
return c.concat(
|
||||||
lodash.keys(this.sharedLabels.fields.report_type.choices).sort().map((v) => {
|
choices.sort().map((v) => {
|
||||||
return {
|
return {
|
||||||
value: v,
|
value: v,
|
||||||
label: this.sharedLabels.fields.report_type.choices[v]
|
label: this.sharedLabels.fields.report_type.choices[v] || v
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
<template>
|
||||||
|
<modal @update:show="update" :show="$store.state.moderation.showReportModal">
|
||||||
|
<h2 class="ui header" v-if="target">
|
||||||
|
<translate translate-context="Popup/Moderation/Title/Verb">Do you want to report this object?</translate>
|
||||||
|
<div class="ui sub header">
|
||||||
|
{{ target.typeLabel }} - {{ target.label }}
|
||||||
|
</div>
|
||||||
|
</h2>
|
||||||
|
<div class="scrolling content">
|
||||||
|
<div class="description">
|
||||||
|
<div v-if="errors.length > 0" class="ui negative message">
|
||||||
|
<div class="header"><translate translate-context="Popup/Moderation/Error message">Error while submitting report</translate></div>
|
||||||
|
<ul class="list">
|
||||||
|
<li v-for="error in errors">{{ error }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<translate translate-context="*/Moderation/Popup,Paragraph">Use this form to submit a report to our moderation team.</translate>
|
||||||
|
</p>
|
||||||
|
<form v-if="canSubmit" id="report-form" class="ui form" @submit.prevent="submit">
|
||||||
|
<div v-if="!$store.state.auth.authenticated" class="ui inline required field">
|
||||||
|
<label for="report-submitter-email">
|
||||||
|
<translate translate-context="Content/*/*/Noun">Email</translate>
|
||||||
|
</label>
|
||||||
|
<input type="email" v-model="submitterEmail" name="report-submitter-email" id="report-submitter-email" required>
|
||||||
|
</div>
|
||||||
|
<report-category-dropdown
|
||||||
|
class="ui inline required field"
|
||||||
|
v-model="category"
|
||||||
|
:required="true"
|
||||||
|
:empty="true"
|
||||||
|
:restrict-to="allowedCategories"
|
||||||
|
:label="true"></report-category-dropdown>
|
||||||
|
<div class="ui field">
|
||||||
|
<label for="report-summary">
|
||||||
|
<translate translate-context="*/*/Field.Label/Noun">Message</translate>
|
||||||
|
</label>
|
||||||
|
<p>
|
||||||
|
<translate translate-context="*/*/Field,Help">Use this field to provide additional context to the moderator that will handle your report.</translate>
|
||||||
|
</p>
|
||||||
|
<textarea name="report-summary" id="report-summary" rows="8" v-model="summary"></textarea>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div v-else-if="isLoadingReportTypes" class="ui inline active loader">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div v-else class="ui warning message">
|
||||||
|
<div class="header">
|
||||||
|
<translate translate-context="Popup/Moderation/Error message">Anonymous reports are disabled, please sign-in to submit a report.</translate>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<div class="ui cancel button"><translate translate-context="*/*/Button.Label/Verb">Cancel</translate></div>
|
||||||
|
<button
|
||||||
|
v-if="canSubmit"
|
||||||
|
:class="['ui', 'green', {loading: isLoading}, 'button']"
|
||||||
|
type="submit" form="report-form">
|
||||||
|
<translate translate-context="Popup/*/Button.Label">Submit report</translate>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import _ from '@/lodash'
|
||||||
|
import axios from 'axios'
|
||||||
|
import {mapState} from 'vuex'
|
||||||
|
|
||||||
|
import logger from '@/logging'
|
||||||
|
import Modal from '@/components/semantic/Modal'
|
||||||
|
import ReportCategoryDropdown from '@/components/moderation/ReportCategoryDropdown'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Modal,
|
||||||
|
ReportCategoryDropdown,
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
formKey: String(new Date()),
|
||||||
|
errors: [],
|
||||||
|
isLoading: false,
|
||||||
|
isLoadingReportTypes: false,
|
||||||
|
summary: '',
|
||||||
|
submitterEmail: '',
|
||||||
|
category: null,
|
||||||
|
reportTypes: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
target: state => state.moderation.reportModalTarget,
|
||||||
|
}),
|
||||||
|
allowedCategories () {
|
||||||
|
if (this.$store.state.auth.authenticated) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return this.reportTypes.filter((t) => {
|
||||||
|
return t.anonymous === true
|
||||||
|
}).map((c) => {
|
||||||
|
return c.type
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
canSubmit () {
|
||||||
|
if (this.$store.state.auth.authenticated) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.allowedCategories.length > 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
update (v) {
|
||||||
|
this.$store.commit('moderation/showReportModal', v)
|
||||||
|
this.errors = []
|
||||||
|
},
|
||||||
|
submit () {
|
||||||
|
let self = this
|
||||||
|
self.isLoading = true
|
||||||
|
let payload = {
|
||||||
|
target: this.target,
|
||||||
|
summary: this.summary,
|
||||||
|
type: this.category,
|
||||||
|
}
|
||||||
|
if (!this.$store.state.auth.authenticated) {
|
||||||
|
payload.submitter_email = this.submitterEmail
|
||||||
|
}
|
||||||
|
return axios.post('moderation/reports/', payload).then(response => {
|
||||||
|
self.update(false)
|
||||||
|
self.isLoading = false
|
||||||
|
let msg = this.$pgettext('*/Moderation/Message', 'Report successfully submitted, thank you')
|
||||||
|
self.$store.commit('moderation/contentFilter', response.data)
|
||||||
|
self.$store.commit('ui/addMessage', {
|
||||||
|
content: msg,
|
||||||
|
date: new Date()
|
||||||
|
})
|
||||||
|
self.summary = ''
|
||||||
|
self.category = ''
|
||||||
|
}, error => {
|
||||||
|
self.errors = error.backendErrors
|
||||||
|
self.isLoading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'$store.state.moderation.showReportModal': function (v) {
|
||||||
|
if (!v || this.$store.state.auth.authenticated) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let self = this
|
||||||
|
self.isLoadingReportTypes = true
|
||||||
|
axios.get('instance/nodeinfo/2.0/').then(response => {
|
||||||
|
self.isLoadingReportTypes = false
|
||||||
|
self.reportTypes = response.data.metadata.reportTypes || []
|
||||||
|
}, error => {
|
||||||
|
self.isLoadingReportTypes = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
|
@ -5,8 +5,18 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="right floated">
|
<div class="right floated">
|
||||||
<play-button :is-playable="playlist.is_playable" :icon-only="true" class="ui inline" :button-classes="['ui', 'circular', 'large', {orange: playlist.tracks_count > 0}, 'icon', 'button', {disabled: playlist.tracks_count === 0}]" :playlist="playlist"></play-button>
|
<play-button
|
||||||
<play-button :is-playable="playlist.is_playable" class="basic inline icon" :dropdown-only="true" :dropdown-icon-classes="['ellipsis', 'vertical', 'large', {disabled: playlist.tracks_count === 0}, 'grey']" :playlist="playlist"></play-button>
|
:is-playable="playlist.is_playable"
|
||||||
|
:icon-only="true" class="ui inline"
|
||||||
|
:button-classes="['ui', 'circular', 'large', {orange: playlist.tracks_count > 0}, 'icon', 'button', {disabled: playlist.tracks_count === 0}]"
|
||||||
|
:playlist="playlist"></play-button>
|
||||||
|
<play-button
|
||||||
|
:is-playable="playlist.is_playable"
|
||||||
|
class="basic inline icon"
|
||||||
|
:dropdown-only="true"
|
||||||
|
:dropdown-icon-classes="['ellipsis', 'vertical', 'large', {disabled: playlist.tracks_count === 0}, 'grey']"
|
||||||
|
:account="playlist.actor"
|
||||||
|
:playlist="playlist"></play-button>
|
||||||
</div>
|
</div>
|
||||||
<router-link :title="playlist.name" class="discrete link" :to="{name: 'library.playlists.detail', params: {id: playlist.id }}">
|
<router-link :title="playlist.name" class="discrete link" :to="{name: 'library.playlists.detail', params: {id: playlist.id }}">
|
||||||
{{ playlist.name | truncate(30) }}
|
{{ playlist.name | truncate(30) }}
|
||||||
|
|
|
@ -7,16 +7,24 @@ export default {
|
||||||
state: {
|
state: {
|
||||||
filters: [],
|
filters: [],
|
||||||
showFilterModal: false,
|
showFilterModal: false,
|
||||||
|
showReportModal: false,
|
||||||
lastUpdate: new Date(),
|
lastUpdate: new Date(),
|
||||||
filterModalTarget: {
|
filterModalTarget: {
|
||||||
type: null,
|
type: null,
|
||||||
target: null,
|
target: null,
|
||||||
|
},
|
||||||
|
reportModalTarget: {
|
||||||
|
type: null,
|
||||||
|
target: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
filterModalTarget (state, value) {
|
filterModalTarget (state, value) {
|
||||||
state.filterModalTarget = value
|
state.filterModalTarget = value
|
||||||
},
|
},
|
||||||
|
reportModalTarget (state, value) {
|
||||||
|
state.reportModalTarget = value
|
||||||
|
},
|
||||||
empty (state) {
|
empty (state) {
|
||||||
state.filters = []
|
state.filters = []
|
||||||
},
|
},
|
||||||
|
@ -35,10 +43,21 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
showReportModal (state, value) {
|
||||||
|
state.showReportModal = value
|
||||||
|
if (!value) {
|
||||||
|
state.reportModalTarget = {
|
||||||
|
type: null,
|
||||||
|
target: null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
reset (state) {
|
reset (state) {
|
||||||
state.filters = []
|
state.filters = []
|
||||||
state.filterModalTarget = null
|
state.filterModalTarget = null
|
||||||
state.showFilterModal = false
|
state.showFilterModal = false
|
||||||
|
state.showReportModal = false
|
||||||
|
state.reportModalTarget = {}
|
||||||
},
|
},
|
||||||
deleteContentFilter (state, uuid) {
|
deleteContentFilter (state, uuid) {
|
||||||
state.filters = state.filters.filter((e) => {
|
state.filters = state.filters.filter((e) => {
|
||||||
|
@ -61,6 +80,10 @@ export default {
|
||||||
commit('filterModalTarget', payload)
|
commit('filterModalTarget', payload)
|
||||||
commit('showFilterModal', true)
|
commit('showFilterModal', true)
|
||||||
},
|
},
|
||||||
|
report ({commit}, payload) {
|
||||||
|
commit('reportModalTarget', payload)
|
||||||
|
commit('showReportModal', true)
|
||||||
|
},
|
||||||
fetchContentFilters ({dispatch, state, commit, rootState}, url) {
|
fetchContentFilters ({dispatch, state, commit, rootState}, url) {
|
||||||
let params = {}
|
let params = {}
|
||||||
let promise
|
let promise
|
||||||
|
|
Loading…
Reference in New Issue