Fix frontend federation search
This commit is contained in:
parent
40cb7aadb3
commit
f6faed0691
|
@ -20659,18 +20659,32 @@ components:
|
||||||
format: date-time
|
format: date-time
|
||||||
readOnly: true
|
readOnly: true
|
||||||
nullable: true
|
nullable: true
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
object:
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/Artist'
|
||||||
|
- $ref: '#/components/schemas/Album'
|
||||||
|
- $ref: '#/components/schemas/Track'
|
||||||
|
- $ref: '#/components/schemas/APIActor'
|
||||||
|
- $ref: '#/components/schemas/Channel'
|
||||||
|
- $ref: '#/components/schemas/Playlist'
|
||||||
|
readOnly: true
|
||||||
required:
|
required:
|
||||||
- actor
|
- actor
|
||||||
- creation_date
|
- creation_date
|
||||||
- detail
|
- detail
|
||||||
- fetch_date
|
- fetch_date
|
||||||
- id
|
- id
|
||||||
|
- object
|
||||||
- status
|
- status
|
||||||
|
- type
|
||||||
- url
|
- url
|
||||||
FetchRequest:
|
FetchRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
object:
|
object_uri:
|
||||||
type: string
|
type: string
|
||||||
writeOnly: true
|
writeOnly: true
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
@ -20679,7 +20693,7 @@ components:
|
||||||
writeOnly: true
|
writeOnly: true
|
||||||
default: false
|
default: false
|
||||||
required:
|
required:
|
||||||
- object
|
- object_uri
|
||||||
FetchStatusEnum:
|
FetchStatusEnum:
|
||||||
enum:
|
enum:
|
||||||
- pending
|
- pending
|
||||||
|
|
|
@ -10,9 +10,10 @@ from drf_spectacular.utils import extend_schema_field
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from funkwhale_api.audio import models as audio_models
|
from funkwhale_api.audio import models as audio_models
|
||||||
from funkwhale_api.common import fields as common_fields
|
from funkwhale_api.audio import serializers as audio_serializers
|
||||||
from funkwhale_api.common import serializers as common_serializers
|
from funkwhale_api.common import serializers as common_serializers
|
||||||
from funkwhale_api.music import models as music_models
|
from funkwhale_api.music import models as music_models
|
||||||
|
from funkwhale_api.playlists import models as playlists_models
|
||||||
from funkwhale_api.users import serializers as users_serializers
|
from funkwhale_api.users import serializers as users_serializers
|
||||||
|
|
||||||
from . import filters, models
|
from . import filters, models
|
||||||
|
@ -192,19 +193,17 @@ class InboxItemActionSerializer(common_serializers.ActionSerializer):
|
||||||
return objects.update(is_read=True)
|
return objects.update(is_read=True)
|
||||||
|
|
||||||
|
|
||||||
FETCH_OBJECT_CONFIG = {
|
OBJECT_SERIALIZER_MAPPING = {
|
||||||
"artist": {"queryset": music_models.Artist.objects.all()},
|
music_models.Artist: federation_serializers.ArtistSerializer,
|
||||||
"album": {"queryset": music_models.Album.objects.all()},
|
music_models.Album: federation_serializers.AlbumSerializer,
|
||||||
"track": {"queryset": music_models.Track.objects.all()},
|
music_models.Track: federation_serializers.TrackSerializer,
|
||||||
"library": {"queryset": music_models.Library.objects.all(), "id_attr": "uuid"},
|
models.Actor: federation_serializers.APIActorSerializer,
|
||||||
"upload": {"queryset": music_models.Upload.objects.all(), "id_attr": "uuid"},
|
audio_models.Channel: audio_serializers.ChannelSerializer,
|
||||||
"account": {"queryset": models.Actor.objects.all(), "id_attr": "full_username"},
|
playlists_models.Playlist: federation_serializers.PlaylistSerializer,
|
||||||
"channel": {"queryset": audio_models.Channel.objects.all(), "id_attr": "uuid"},
|
|
||||||
}
|
}
|
||||||
FETCH_OBJECT_FIELD = common_fields.GenericRelation(FETCH_OBJECT_CONFIG)
|
|
||||||
|
|
||||||
|
|
||||||
def convert_url_to_webginfer(url):
|
def convert_url_to_webfinger(url):
|
||||||
parsed_url = urlparse(url)
|
parsed_url = urlparse(url)
|
||||||
domain = parsed_url.netloc # e.g., "node1.funkwhale.test"
|
domain = parsed_url.netloc # e.g., "node1.funkwhale.test"
|
||||||
path_parts = parsed_url.path.strip("/").split("/")
|
path_parts = parsed_url.path.strip("/").split("/")
|
||||||
|
@ -217,7 +216,9 @@ def convert_url_to_webginfer(url):
|
||||||
|
|
||||||
class FetchSerializer(serializers.ModelSerializer):
|
class FetchSerializer(serializers.ModelSerializer):
|
||||||
actor = federation_serializers.APIActorSerializer(read_only=True)
|
actor = federation_serializers.APIActorSerializer(read_only=True)
|
||||||
object = serializers.CharField(write_only=True)
|
object_uri = serializers.CharField(required=True, write_only=True)
|
||||||
|
object = serializers.SerializerMethodField(read_only=True)
|
||||||
|
type = serializers.SerializerMethodField(read_only=True)
|
||||||
force = serializers.BooleanField(default=False, required=False, write_only=True)
|
force = serializers.BooleanField(default=False, required=False, write_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -230,8 +231,10 @@ class FetchSerializer(serializers.ModelSerializer):
|
||||||
"detail",
|
"detail",
|
||||||
"creation_date",
|
"creation_date",
|
||||||
"fetch_date",
|
"fetch_date",
|
||||||
"object",
|
"object_uri",
|
||||||
"force",
|
"force",
|
||||||
|
"type",
|
||||||
|
"object",
|
||||||
]
|
]
|
||||||
read_only_fields = [
|
read_only_fields = [
|
||||||
"id",
|
"id",
|
||||||
|
@ -241,14 +244,36 @@ class FetchSerializer(serializers.ModelSerializer):
|
||||||
"detail",
|
"detail",
|
||||||
"creation_date",
|
"creation_date",
|
||||||
"fetch_date",
|
"fetch_date",
|
||||||
|
"type",
|
||||||
|
"object",
|
||||||
]
|
]
|
||||||
|
|
||||||
def validate_object(self, value):
|
def get_type(self, fetch):
|
||||||
|
obj = fetch.object
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Return the type as a string
|
||||||
|
if isinstance(obj, music_models.Artist):
|
||||||
|
return "artist"
|
||||||
|
elif isinstance(obj, music_models.Album):
|
||||||
|
return "album"
|
||||||
|
elif isinstance(obj, music_models.Track):
|
||||||
|
return "track"
|
||||||
|
elif isinstance(obj, models.Actor):
|
||||||
|
return "account"
|
||||||
|
elif isinstance(obj, audio_models.Channel):
|
||||||
|
return "channel"
|
||||||
|
elif isinstance(obj, playlists_models.Playlist):
|
||||||
|
return "playlist"
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def validate_object_uri(self, value):
|
||||||
if value.startswith("https://"):
|
if value.startswith("https://"):
|
||||||
converted = convert_url_to_webginfer(value)
|
converted = convert_url_to_webfinger(value)
|
||||||
if converted:
|
if converted:
|
||||||
value = converted
|
value = converted
|
||||||
# if value is a webginfer lookup, we craft a special url
|
|
||||||
if value.startswith("@"):
|
if value.startswith("@"):
|
||||||
value = value.lstrip("@")
|
value = value.lstrip("@")
|
||||||
validator = validators.EmailValidator()
|
validator = validators.EmailValidator()
|
||||||
|
@ -256,9 +281,30 @@ class FetchSerializer(serializers.ModelSerializer):
|
||||||
validator(value)
|
validator(value)
|
||||||
except validators.ValidationError:
|
except validators.ValidationError:
|
||||||
return value
|
return value
|
||||||
|
|
||||||
return f"webfinger://{value}"
|
return f"webfinger://{value}"
|
||||||
|
|
||||||
|
@extend_schema_field(
|
||||||
|
{
|
||||||
|
"oneOf": [
|
||||||
|
{"$ref": "#/components/schemas/Artist"},
|
||||||
|
{"$ref": "#/components/schemas/Album"},
|
||||||
|
{"$ref": "#/components/schemas/Track"},
|
||||||
|
{"$ref": "#/components/schemas/APIActor"},
|
||||||
|
{"$ref": "#/components/schemas/Channel"},
|
||||||
|
{"$ref": "#/components/schemas/Playlist"},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
def get_object(self, fetch):
|
||||||
|
obj = fetch.object
|
||||||
|
if obj is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
serializer_class = OBJECT_SERIALIZER_MAPPING.get(type(obj))
|
||||||
|
if serializer_class:
|
||||||
|
return serializer_class(obj).data
|
||||||
|
return None
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
check_duplicates = not validated_data.get("force", False)
|
check_duplicates = not validated_data.get("force", False)
|
||||||
if check_duplicates:
|
if check_duplicates:
|
||||||
|
@ -267,7 +313,7 @@ class FetchSerializer(serializers.ModelSerializer):
|
||||||
validated_data["actor"]
|
validated_data["actor"]
|
||||||
.fetches.filter(
|
.fetches.filter(
|
||||||
status="finished",
|
status="finished",
|
||||||
url=validated_data["object"],
|
url=validated_data["object_uri"],
|
||||||
creation_date__gte=timezone.now()
|
creation_date__gte=timezone.now()
|
||||||
- datetime.timedelta(
|
- datetime.timedelta(
|
||||||
seconds=settings.FEDERATION_DUPLICATE_FETCH_DELAY
|
seconds=settings.FEDERATION_DUPLICATE_FETCH_DELAY
|
||||||
|
@ -280,18 +326,10 @@ class FetchSerializer(serializers.ModelSerializer):
|
||||||
return duplicate
|
return duplicate
|
||||||
|
|
||||||
fetch = models.Fetch.objects.create(
|
fetch = models.Fetch.objects.create(
|
||||||
actor=validated_data["actor"], url=validated_data["object"]
|
actor=validated_data["actor"], url=validated_data["object_uri"]
|
||||||
)
|
)
|
||||||
return fetch
|
return fetch
|
||||||
|
|
||||||
def to_representation(self, obj):
|
|
||||||
repr = super().to_representation(obj)
|
|
||||||
object_data = None
|
|
||||||
if obj.object:
|
|
||||||
object_data = FETCH_OBJECT_FIELD.to_representation(obj.object)
|
|
||||||
repr["object"] = object_data
|
|
||||||
return repr
|
|
||||||
|
|
||||||
|
|
||||||
class FullActorSerializer(serializers.Serializer):
|
class FullActorSerializer(serializers.Serializer):
|
||||||
fid = serializers.URLField()
|
fid = serializers.URLField()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from funkwhale_api.audio.serializers import ChannelSerializer
|
||||||
from funkwhale_api.common import serializers as common_serializers
|
from funkwhale_api.common import serializers as common_serializers
|
||||||
from funkwhale_api.federation import api_serializers, serializers
|
from funkwhale_api.federation import api_serializers, serializers
|
||||||
from funkwhale_api.users import serializers as users_serializers
|
from funkwhale_api.users import serializers as users_serializers
|
||||||
|
@ -128,6 +129,7 @@ def test_fetch_serializer_no_obj(factories, to_api_date):
|
||||||
"status": fetch.status,
|
"status": fetch.status,
|
||||||
"detail": fetch.detail,
|
"detail": fetch.detail,
|
||||||
"object": None,
|
"object": None,
|
||||||
|
"type": None,
|
||||||
"actor": serializers.APIActorSerializer(fetch.actor).data,
|
"actor": serializers.APIActorSerializer(fetch.actor).data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,22 +137,28 @@ def test_fetch_serializer_no_obj(factories, to_api_date):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"object_factory, expected_type, expected_id",
|
"object_factory, expected_type, serializer_class",
|
||||||
[
|
[
|
||||||
("music.Album", "album", "id"),
|
("music.Album", "album", serializers.AlbumSerializer),
|
||||||
("music.Artist", "artist", "id"),
|
("music.Artist", "artist", serializers.ArtistSerializer),
|
||||||
("music.Track", "track", "id"),
|
("music.Track", "track", serializers.TrackSerializer),
|
||||||
("music.Library", "library", "uuid"),
|
("audio.Channel", "channel", ChannelSerializer),
|
||||||
("music.Upload", "upload", "uuid"),
|
("federation.Actor", "account", serializers.APIActorSerializer),
|
||||||
("audio.Channel", "channel", "uuid"),
|
("playlists.Playlist", "playlist", serializers.PlaylistSerializer),
|
||||||
("federation.Actor", "account", "full_username"),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_fetch_serializer_with_object(
|
def test_fetch_serializer_with_object(
|
||||||
object_factory, expected_type, expected_id, factories, to_api_date
|
object_factory, expected_type, serializer_class, factories, to_api_date
|
||||||
):
|
):
|
||||||
obj = factories[object_factory]()
|
obj = factories[object_factory]()
|
||||||
fetch = factories["federation.Fetch"](object=obj)
|
fetch = factories["federation.Fetch"](object=obj)
|
||||||
|
|
||||||
|
# Serialize the object
|
||||||
|
if serializer_class:
|
||||||
|
object_data = serializer_class(obj).data
|
||||||
|
else:
|
||||||
|
object_data = {"uuid": getattr(obj, "uuid", None)}
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
"id": fetch.pk,
|
"id": fetch.pk,
|
||||||
"url": fetch.url,
|
"url": fetch.url,
|
||||||
|
@ -158,7 +166,10 @@ def test_fetch_serializer_with_object(
|
||||||
"fetch_date": None,
|
"fetch_date": None,
|
||||||
"status": fetch.status,
|
"status": fetch.status,
|
||||||
"detail": fetch.detail,
|
"detail": fetch.detail,
|
||||||
"object": {"type": expected_type, expected_id: getattr(obj, expected_id)},
|
"object": {
|
||||||
|
**object_data,
|
||||||
|
},
|
||||||
|
"type": expected_type,
|
||||||
"actor": serializers.APIActorSerializer(fetch.actor).data,
|
"actor": serializers.APIActorSerializer(fetch.actor).data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,6 +186,7 @@ def test_fetch_serializer_unhandled_obj(factories, to_api_date):
|
||||||
"status": fetch.status,
|
"status": fetch.status,
|
||||||
"detail": fetch.detail,
|
"detail": fetch.detail,
|
||||||
"object": None,
|
"object": None,
|
||||||
|
"type": None,
|
||||||
"actor": serializers.APIActorSerializer(fetch.actor).data,
|
"actor": serializers.APIActorSerializer(fetch.actor).data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -243,7 +243,7 @@ def test_can_fetch_using_url_synchronous(
|
||||||
fetch_task = mocker.patch.object(tasks, "fetch", side_effect=fake_task)
|
fetch_task = mocker.patch.object(tasks, "fetch", side_effect=fake_task)
|
||||||
|
|
||||||
url = reverse("api:v1:federation:fetches-list")
|
url = reverse("api:v1:federation:fetches-list")
|
||||||
data = {"object": object_id}
|
data = {"object_uri": object_id}
|
||||||
response = logged_in_api_client.post(url, data)
|
response = logged_in_api_client.post(url, data)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
@ -266,7 +266,7 @@ def test_fetch_duplicate(factories, logged_in_api_client, settings, now):
|
||||||
creation_date=now - datetime.timedelta(seconds=59),
|
creation_date=now - datetime.timedelta(seconds=59),
|
||||||
)
|
)
|
||||||
url = reverse("api:v1:federation:fetches-list")
|
url = reverse("api:v1:federation:fetches-list")
|
||||||
data = {"object": object_id}
|
data = {"object_uri": object_id}
|
||||||
response = logged_in_api_client.post(url, data)
|
response = logged_in_api_client.post(url, data)
|
||||||
assert response.status_code == 201
|
assert response.status_code == 201
|
||||||
assert response.data == api_serializers.FetchSerializer(duplicate).data
|
assert response.data == api_serializers.FetchSerializer(duplicate).data
|
||||||
|
@ -286,7 +286,7 @@ def test_fetch_duplicate_bypass_with_force(
|
||||||
creation_date=now - datetime.timedelta(seconds=59),
|
creation_date=now - datetime.timedelta(seconds=59),
|
||||||
)
|
)
|
||||||
url = reverse("api:v1:federation:fetches-list")
|
url = reverse("api:v1:federation:fetches-list")
|
||||||
data = {"object": object_id, "force": True}
|
data = {"object_uri": object_id, "force": True}
|
||||||
response = logged_in_api_client.post(url, data)
|
response = logged_in_api_client.post(url, data)
|
||||||
|
|
||||||
fetch = actor.fetches.latest("id")
|
fetch = actor.fetches.latest("id")
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Entering an input in the global search in the sidebar opens up a modal that show all possible results at once with collapseable sections for each category, including federated searches for other users, channels and rss feeds. (#2910)
|
|
@ -6638,9 +6638,11 @@ export interface components {
|
||||||
readonly creation_date: string;
|
readonly creation_date: string;
|
||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
readonly fetch_date: string | null;
|
readonly fetch_date: string | null;
|
||||||
|
readonly type: string;
|
||||||
|
readonly object: components["schemas"]["Artist"] | components["schemas"]["Album"] | components["schemas"]["Track"] | components["schemas"]["APIActor"] | components["schemas"]["Channel"] | components["schemas"]["Playlist"];
|
||||||
};
|
};
|
||||||
FetchRequest: {
|
FetchRequest: {
|
||||||
object: string;
|
object_uri: string;
|
||||||
/** @default false */
|
/** @default false */
|
||||||
force: boolean;
|
force: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
},
|
},
|
||||||
"vui": {
|
"vui": {
|
||||||
"tracks": "{n}টি গান | {n}টি গান",
|
"tracks": "{n}টি গান | {n}টি গান",
|
||||||
"by-user": "{'@'}{username} থেকে",
|
"by-user": "{username} থেকে",
|
||||||
"aria": {
|
"aria": {
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"gotoPage": "{n} পৃষ্ঠায় যাও",
|
"gotoPage": "{n} পৃষ্ঠায় যাও",
|
||||||
|
|
|
@ -2,6 +2,27 @@
|
||||||
"App": {
|
"App": {
|
||||||
"loading": "Carregant…"
|
"loading": "Carregant…"
|
||||||
},
|
},
|
||||||
|
"vui": {
|
||||||
|
"radio": "Ràdio",
|
||||||
|
"albums": "{n} àlbum | {n} àlbums",
|
||||||
|
"tracks": "{n} pista | {n} pistes",
|
||||||
|
"episodes": "{n} episodi | {n} episodis",
|
||||||
|
"by-user": "per {username}",
|
||||||
|
"go-to": "Anar a",
|
||||||
|
"pagination": {
|
||||||
|
"previous": "Prèvia",
|
||||||
|
"next": "Següent"
|
||||||
|
},
|
||||||
|
"aria": {
|
||||||
|
"pagination": {
|
||||||
|
"nav": "Navegació per Paginació",
|
||||||
|
"gotoPage": "Anar a Pàgina {n}",
|
||||||
|
"gotoPrevious": "Anar a la Pàgina Prèvia",
|
||||||
|
"gotoNext": "Anar a la Pàgina Següent",
|
||||||
|
"currentPage": "Pàgina actual, Pàgina {n}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"About": {
|
"About": {
|
||||||
"description": {
|
"description": {
|
||||||
|
@ -4629,26 +4650,5 @@
|
||||||
"title": "Ràdio"
|
"title": "Ràdio"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"vui": {
|
|
||||||
"albums": "{n} àlbum | {n} àlbums",
|
|
||||||
"aria": {
|
|
||||||
"pagination": {
|
|
||||||
"currentPage": "Pàgina actual, Pàgina {n}",
|
|
||||||
"gotoNext": "Anar a la Pàgina Següent",
|
|
||||||
"gotoPage": "Anar a Pàgina {n}",
|
|
||||||
"gotoPrevious": "Anar a la Pàgina Prèvia",
|
|
||||||
"nav": "Navegació per Paginació"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"by-user": "per {'@'}{username}",
|
|
||||||
"episodes": "{n} episodi | {n} episodis",
|
|
||||||
"go-to": "Anar a",
|
|
||||||
"pagination": {
|
|
||||||
"next": "Següent",
|
|
||||||
"previous": "Prèvia"
|
|
||||||
},
|
|
||||||
"radio": "Ràdio",
|
|
||||||
"tracks": "{n} pista | {n} pistes"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"radio": "Ràdio",
|
"radio": "Ràdio",
|
||||||
"albums": "{n} àlbum | {n} àlbums",
|
"albums": "{n} àlbum | {n} àlbums",
|
||||||
"episodes": "{n} episodi | {n} episodis",
|
"episodes": "{n} episodi | {n} episodis",
|
||||||
"by-user": "per {'@'}{username}",
|
"by-user": "per {username}",
|
||||||
"aria": {
|
"aria": {
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"gotoPrevious": "Retornar a la pàgina anterior",
|
"gotoPrevious": "Retornar a la pàgina anterior",
|
||||||
|
|
|
@ -4623,7 +4623,7 @@
|
||||||
"nav": "Nummerierte Navigation"
|
"nav": "Nummerierte Navigation"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"by-user": "von {'@'}{username}",
|
"by-user": "von {username}",
|
||||||
"episodes": "{n} Episode | {n} Episoden",
|
"episodes": "{n} Episode | {n} Episoden",
|
||||||
"go-to": "Gehe zu",
|
"go-to": "Gehe zu",
|
||||||
"pagination": {
|
"pagination": {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"albums": "{n} album | {n} albums",
|
"albums": "{n} album | {n} albums",
|
||||||
"tracks": "{n} track | {n} tracks",
|
"tracks": "{n} track | {n} tracks",
|
||||||
"episodes": "{n} episode | {n} episodes",
|
"episodes": "{n} episode | {n} episodes",
|
||||||
"by-user": "by {'@'}{username}",
|
"by-user": "by {username}",
|
||||||
"go-to": "Go to",
|
"go-to": "Go to",
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"previous": "Previous",
|
"previous": "Previous",
|
||||||
|
|
|
@ -2,6 +2,27 @@
|
||||||
"App": {
|
"App": {
|
||||||
"loading": "Cargando…"
|
"loading": "Cargando…"
|
||||||
},
|
},
|
||||||
|
"vui": {
|
||||||
|
"radio": "Radio",
|
||||||
|
"albums": "{n} álbum | {n} álbumes",
|
||||||
|
"tracks": "{n} pista | {n} pistas",
|
||||||
|
"episodes": "{n} episodio | {n} episodios",
|
||||||
|
"by-user": "de {username}",
|
||||||
|
"go-to": "Vaya a",
|
||||||
|
"pagination": {
|
||||||
|
"previous": "Anterior",
|
||||||
|
"next": "Siguiente"
|
||||||
|
},
|
||||||
|
"aria": {
|
||||||
|
"pagination": {
|
||||||
|
"nav": "Paginación Navegación",
|
||||||
|
"currentPage": "Página corriente, Página {n}",
|
||||||
|
"gotoPage": "Ir a la página {n}",
|
||||||
|
"gotoPrevious": "Ir a la página anterior",
|
||||||
|
"gotoNext": "Ir a la página siguiente"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"About": {
|
"About": {
|
||||||
"description": {
|
"description": {
|
||||||
|
@ -4422,26 +4443,5 @@
|
||||||
"title": "Radio"
|
"title": "Radio"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"vui": {
|
|
||||||
"albums": "{n} álbum | {n} álbumes",
|
|
||||||
"aria": {
|
|
||||||
"pagination": {
|
|
||||||
"currentPage": "Página corriente, Página {n}",
|
|
||||||
"gotoNext": "Ir a la página siguiente",
|
|
||||||
"gotoPage": "Ir a la página {n}",
|
|
||||||
"gotoPrevious": "Ir a la página anterior",
|
|
||||||
"nav": "Paginación Navegación"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"by-user": "de {'@'}{username}",
|
|
||||||
"episodes": "{n} episodio | {n} episodios",
|
|
||||||
"go-to": "Vaya a",
|
|
||||||
"pagination": {
|
|
||||||
"next": "Siguiente",
|
|
||||||
"previous": "Anterior"
|
|
||||||
},
|
|
||||||
"radio": "Radio",
|
|
||||||
"tracks": "{n} pista | {n} pistas"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,27 @@
|
||||||
"App": {
|
"App": {
|
||||||
"loading": "Kargatzen…"
|
"loading": "Kargatzen…"
|
||||||
},
|
},
|
||||||
|
"vui": {
|
||||||
|
"tracks": "Pista {n} | {n} pista",
|
||||||
|
"episodes": "Atal {n} | {n} atal",
|
||||||
|
"by-user": "honen eskutik: {username}",
|
||||||
|
"go-to": "Joan hona:",
|
||||||
|
"pagination": {
|
||||||
|
"previous": "Aurrekoa",
|
||||||
|
"next": "Hurrengoa"
|
||||||
|
},
|
||||||
|
"aria": {
|
||||||
|
"pagination": {
|
||||||
|
"nav": "Orrikatzearen nabigazioa",
|
||||||
|
"gotoPage": "Joan orri honetara: {n}",
|
||||||
|
"gotoPrevious": "Joan aurreko orrira",
|
||||||
|
"gotoNext": "Joan hurrengo orrira",
|
||||||
|
"currentPage": "Uneko orria: {n}"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"albums": "Album {n} | {n} album",
|
||||||
|
"radio": "Irratia"
|
||||||
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"About": {
|
"About": {
|
||||||
"description": {
|
"description": {
|
||||||
|
@ -4611,26 +4632,5 @@
|
||||||
"title": "Irratia"
|
"title": "Irratia"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"vui": {
|
|
||||||
"albums": "Album {n} | {n} album",
|
|
||||||
"aria": {
|
|
||||||
"pagination": {
|
|
||||||
"currentPage": "Uneko orria: {n}",
|
|
||||||
"gotoNext": "Joan hurrengo orrira",
|
|
||||||
"gotoPage": "Joan orri honetara: {n}",
|
|
||||||
"gotoPrevious": "Joan aurreko orrira",
|
|
||||||
"nav": "Orrikatzearen nabigazioa"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"by-user": "honen eskutik: {'@'}{username}",
|
|
||||||
"episodes": "Atal {n} | {n} atal",
|
|
||||||
"go-to": "Joan hona:",
|
|
||||||
"pagination": {
|
|
||||||
"next": "Hurrengoa",
|
|
||||||
"previous": "Aurrekoa"
|
|
||||||
},
|
|
||||||
"radio": "Irratia",
|
|
||||||
"tracks": "Pista {n} | {n} pista"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,32 @@
|
||||||
"App": {
|
"App": {
|
||||||
"loading": "Laden…"
|
"loading": "Laden…"
|
||||||
},
|
},
|
||||||
|
"vui": {
|
||||||
|
"radio": "Radio",
|
||||||
|
"albums": "{n} album | {n} albums",
|
||||||
|
"tracks": "{n} nummer | {n} nummers",
|
||||||
|
"episodes": "{n} aflevering | {n} afleveringen",
|
||||||
|
"by-user": "door {username}",
|
||||||
|
"go-to": "Ga naar",
|
||||||
|
"pagination": {
|
||||||
|
"previous": "Vorige",
|
||||||
|
"next": "Volgende"
|
||||||
|
},
|
||||||
|
"privacy-level": {
|
||||||
|
"private": "Privé",
|
||||||
|
"public": "Openbaar",
|
||||||
|
"pod": "pod"
|
||||||
|
},
|
||||||
|
"aria": {
|
||||||
|
"pagination": {
|
||||||
|
"nav": "Pagina navigatie",
|
||||||
|
"gotoPage": "Ga naar pagina {n}",
|
||||||
|
"gotoPrevious": "Ga naar Vorige Pagina",
|
||||||
|
"gotoNext": "Ga naar Volgende Pagina",
|
||||||
|
"currentPage": "Huidige pagina, pagina {n}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"About": {
|
"About": {
|
||||||
"description": {
|
"description": {
|
||||||
|
@ -4611,31 +4637,5 @@
|
||||||
"title": "Radio"
|
"title": "Radio"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"vui": {
|
|
||||||
"albums": "{n} album | {n} albums",
|
|
||||||
"aria": {
|
|
||||||
"pagination": {
|
|
||||||
"currentPage": "Huidige pagina, pagina {n}",
|
|
||||||
"gotoNext": "Ga naar Volgende Pagina",
|
|
||||||
"gotoPage": "Ga naar pagina {n}",
|
|
||||||
"gotoPrevious": "Ga naar Vorige Pagina",
|
|
||||||
"nav": "Pagina navigatie"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"by-user": "door {'@'}{username}",
|
|
||||||
"episodes": "{n} aflevering | {n} afleveringen",
|
|
||||||
"go-to": "Ga naar",
|
|
||||||
"pagination": {
|
|
||||||
"next": "Volgende",
|
|
||||||
"previous": "Vorige"
|
|
||||||
},
|
|
||||||
"privacy-level": {
|
|
||||||
"pod": "pod",
|
|
||||||
"private": "Privé",
|
|
||||||
"public": "Openbaar"
|
|
||||||
},
|
|
||||||
"radio": "Radio",
|
|
||||||
"tracks": "{n} nummer | {n} nummers"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,27 @@
|
||||||
"App": {
|
"App": {
|
||||||
"loading": "Yükleniyor…"
|
"loading": "Yükleniyor…"
|
||||||
},
|
},
|
||||||
|
"vui": {
|
||||||
|
"radio": "Radyo",
|
||||||
|
"albums": "{n} albüm | {n} albümler",
|
||||||
|
"tracks": "{n} parça | {n} parçalar",
|
||||||
|
"episodes": "{n} bölüm | {n} bölümler",
|
||||||
|
"by-user": "by {username}",
|
||||||
|
"go-to": "Git",
|
||||||
|
"pagination": {
|
||||||
|
"previous": "Önceki",
|
||||||
|
"next": "Sonraki"
|
||||||
|
},
|
||||||
|
"aria": {
|
||||||
|
"pagination": {
|
||||||
|
"nav": "Sayfalandırma Navigasyonu",
|
||||||
|
"gotoPage": "Sayfaya Git {n}",
|
||||||
|
"gotoPrevious": "Önceki Sayfaya Git",
|
||||||
|
"gotoNext": "Sonraki Sayfaya Git",
|
||||||
|
"currentPage": "Geçerli Sayfa, Sayfa {n}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"About": {
|
"About": {
|
||||||
"description": {
|
"description": {
|
||||||
|
@ -2236,26 +2257,5 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"vui": {
|
|
||||||
"albums": "{n} albüm | {n} albümler",
|
|
||||||
"aria": {
|
|
||||||
"pagination": {
|
|
||||||
"currentPage": "Geçerli Sayfa, Sayfa {n}",
|
|
||||||
"gotoNext": "Sonraki Sayfaya Git",
|
|
||||||
"gotoPage": "Sayfaya Git {n}",
|
|
||||||
"gotoPrevious": "Önceki Sayfaya Git",
|
|
||||||
"nav": "Sayfalandırma Navigasyonu"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"by-user": "by {'@'}{username}",
|
|
||||||
"episodes": "{n} bölüm | {n} bölümler",
|
|
||||||
"go-to": "Git",
|
|
||||||
"pagination": {
|
|
||||||
"next": "Sonraki",
|
|
||||||
"previous": "Önceki"
|
|
||||||
},
|
|
||||||
"radio": "Radyo",
|
|
||||||
"tracks": "{n} parça | {n} parçalar"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,27 @@
|
||||||
"App": {
|
"App": {
|
||||||
"loading": "加载中…"
|
"loading": "加载中…"
|
||||||
},
|
},
|
||||||
|
"vui": {
|
||||||
|
"tracks": "{n} 歌曲 | {n} 歌曲",
|
||||||
|
"episodes": "{n} 节目 | {n} 节目",
|
||||||
|
"by-user": "由 {username}",
|
||||||
|
"go-to": "去",
|
||||||
|
"pagination": {
|
||||||
|
"previous": "以前的",
|
||||||
|
"next": "下一个"
|
||||||
|
},
|
||||||
|
"aria": {
|
||||||
|
"pagination": {
|
||||||
|
"nav": "分页导航",
|
||||||
|
"gotoPage": "转到页面 {n}",
|
||||||
|
"gotoPrevious": "转到上一页",
|
||||||
|
"gotoNext": "转到下一页",
|
||||||
|
"currentPage": "当前页面,第 {n} 页"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"radio": "电台",
|
||||||
|
"albums": "{n} 专辑 | {n} 专辑"
|
||||||
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"About": {
|
"About": {
|
||||||
"description": {
|
"description": {
|
||||||
|
@ -4517,26 +4538,5 @@
|
||||||
"title": "电台"
|
"title": "电台"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"vui": {
|
|
||||||
"albums": "{n} 专辑 | {n} 专辑",
|
|
||||||
"aria": {
|
|
||||||
"pagination": {
|
|
||||||
"currentPage": "当前页面,第 {n} 页",
|
|
||||||
"gotoNext": "转到下一页",
|
|
||||||
"gotoPage": "转到页面 {n}",
|
|
||||||
"gotoPrevious": "转到上一页",
|
|
||||||
"nav": "分页导航"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"by-user": "由 {'@'}{username}",
|
|
||||||
"episodes": "{n} 节目 | {n} 节目",
|
|
||||||
"go-to": "去",
|
|
||||||
"pagination": {
|
|
||||||
"next": "下一个",
|
|
||||||
"previous": "以前的"
|
|
||||||
},
|
|
||||||
"radio": "电台",
|
|
||||||
"tracks": "{n} 歌曲 | {n} 歌曲"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { paths } from '~/generated/types.ts'
|
import type { paths, components } from '~/generated/types.ts'
|
||||||
import type { RadioConfig } from '~/store/radios'
|
import type { RadioConfig } from '~/store/radios'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { ref, watch, computed } from 'vue'
|
import { ref, watch, computed } from 'vue'
|
||||||
|
@ -9,9 +9,12 @@ import { trim, uniqBy } from 'lodash-es'
|
||||||
import useErrorHandler from '~/composables/useErrorHandler'
|
import useErrorHandler from '~/composables/useErrorHandler'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useModal } from '~/ui/composables/useModal.ts'
|
import { useModal } from '~/ui/composables/useModal.ts'
|
||||||
|
import { useStore } from '~/store'
|
||||||
|
|
||||||
import ArtistCard from '~/components/artist/Card.vue'
|
import ArtistCard from '~/components/artist/Card.vue'
|
||||||
import PlaylistCard from '~/components/playlists/Card.vue'
|
import PlaylistCard from '~/components/playlists/Card.vue'
|
||||||
|
import ChannelCard from '~/components/audio/ChannelCard.vue'
|
||||||
|
import ActorLink from '~/components/common/ActorLink.vue'
|
||||||
import TrackTable from '~/components/audio/track/Table.vue'
|
import TrackTable from '~/components/audio/track/Table.vue'
|
||||||
import AlbumCard from '~/components/album/Card.vue'
|
import AlbumCard from '~/components/album/Card.vue'
|
||||||
import RadioCard from '~/components/radios/Card.vue'
|
import RadioCard from '~/components/radios/Card.vue'
|
||||||
|
@ -98,7 +101,8 @@ type Results = {
|
||||||
podcasts: Response['podcasts']['results'],
|
podcasts: Response['podcasts']['results'],
|
||||||
series: Response['series']['results'],
|
series: Response['series']['results'],
|
||||||
rss: [Response['rss']],
|
rss: [Response['rss']],
|
||||||
federation: [Response['federation']]
|
federation: [Response['federation']],
|
||||||
|
type: Category
|
||||||
}
|
}
|
||||||
|
|
||||||
const responses = ref<Partial<Response>>({})
|
const responses = ref<Partial<Response>>({})
|
||||||
|
@ -201,7 +205,7 @@ const categories = computed(() => [
|
||||||
endpoint: '/federation/fetches/',
|
endpoint: '/federation/fetches/',
|
||||||
post: true,
|
post: true,
|
||||||
params: {
|
params: {
|
||||||
object: trimmedQuery.value
|
object_uri: trimmedQuery.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
] as const satisfies {
|
] as const satisfies {
|
||||||
|
@ -222,7 +226,8 @@ const availableCategories = computed(() =>
|
||||||
isFetch.value ? type === 'federation'
|
isFetch.value ? type === 'federation'
|
||||||
: isRss.value ? type === 'rss'
|
: isRss.value ? type === 'rss'
|
||||||
: type !== 'federation' && type !== 'rss'
|
: type !== 'federation' && type !== 'rss'
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
// Whenever available categories change, if there is exactly one, open it
|
// Whenever available categories change, if there is exactly one, open it
|
||||||
watch(availableCategories, () => {
|
watch(availableCategories, () => {
|
||||||
|
@ -245,7 +250,7 @@ const resultsPerCategory = <C extends Category>(category: { type: C }) =>
|
||||||
*/
|
*/
|
||||||
const count = <C extends Category>(category: { type: C }) => (
|
const count = <C extends Category>(category: { type: C }) => (
|
||||||
response => response && 'count' in response ? response.count : resultsPerCategory(category).length
|
response => response && 'count' in response ? response.count : resultsPerCategory(category).length
|
||||||
) (responses.value[category.type])
|
)(responses.value[category.type])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find out whether a category has been queried before
|
* Find out whether a category has been queried before
|
||||||
|
@ -278,6 +283,26 @@ watch(results, () => {
|
||||||
openSections.value = new Set(categoriesWithResults.map(({ type }) => type))
|
openSections.value = new Set(categoriesWithResults.map(({ type }) => type))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Subscribe to an RSS feed
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribe to an RSS feed and return the route for the subscribed channel
|
||||||
|
* @param url The RSS feed URL
|
||||||
|
* @returns The route object for the subscribed channel
|
||||||
|
*/
|
||||||
|
const rssSubscribe = async (url: string) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('channels/rss-subscribe/', { url })
|
||||||
|
store.commit('channels/subscriptions', { uuid: response.data.channel.uuid, value: true })
|
||||||
|
return response.data.channel
|
||||||
|
} catch (error) {
|
||||||
|
useErrorHandler(error as Error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
|
|
||||||
const search = async () => {
|
const search = async () => {
|
||||||
|
@ -321,18 +346,18 @@ const search = async () => {
|
||||||
}
|
}
|
||||||
responses.value[category.type] = response.data
|
responses.value[category.type] = response.data
|
||||||
} else {
|
} else {
|
||||||
|
// TODO: add (@)type key to Response type
|
||||||
if (category.type === 'rss') {
|
if (category.type === 'rss') {
|
||||||
const response = await axios.post<Response['rss']>(
|
const channel = await rssSubscribe(trimmedQuery.value)
|
||||||
category.endpoint,
|
if (channel) {
|
||||||
{ url: trimmedQuery.value }
|
results.value.rss = [channel] // Store the subscribed channel
|
||||||
)
|
}
|
||||||
results.value.rss = [response.data]
|
|
||||||
responses.value[category.type] = response.data
|
|
||||||
} else if (category.type === 'federation') {
|
} else if (category.type === 'federation') {
|
||||||
const response = await axios.post<Response['federation']>(
|
const response = await axios.post<Response['federation']>(
|
||||||
category.endpoint,
|
category.endpoint,
|
||||||
{ params }
|
{ object_uri: trimmedQuery.value }
|
||||||
)
|
)
|
||||||
|
results.value.type = category.type
|
||||||
results.value.federation = [response.data]
|
results.value.federation = [response.data]
|
||||||
responses.value[category.type] = response.data
|
responses.value[category.type] = response.data
|
||||||
} else if (category.type === 'playlists') {
|
} else if (category.type === 'playlists') {
|
||||||
|
@ -340,6 +365,7 @@ const search = async () => {
|
||||||
category.endpoint,
|
category.endpoint,
|
||||||
{ params }
|
{ params }
|
||||||
)
|
)
|
||||||
|
results.value.type = category.type
|
||||||
results.value.playlists = response.data.results
|
results.value.playlists = response.data.results
|
||||||
responses.value[category.type] = response.data
|
responses.value[category.type] = response.data
|
||||||
} else if (category.type === 'podcasts') {
|
} else if (category.type === 'podcasts') {
|
||||||
|
@ -347,6 +373,7 @@ const search = async () => {
|
||||||
category.endpoint,
|
category.endpoint,
|
||||||
{ params }
|
{ params }
|
||||||
)
|
)
|
||||||
|
results.value.type = category.type
|
||||||
results.value.podcasts = response.data.results
|
results.value.podcasts = response.data.results
|
||||||
responses.value[category.type] = response.data
|
responses.value[category.type] = response.data
|
||||||
} else if (category.type === 'radios') {
|
} else if (category.type === 'radios') {
|
||||||
|
@ -354,6 +381,7 @@ const search = async () => {
|
||||||
category.endpoint,
|
category.endpoint,
|
||||||
{ params }
|
{ params }
|
||||||
)
|
)
|
||||||
|
results.value.type = category.type
|
||||||
results.value.radios = response.data.results
|
results.value.radios = response.data.results
|
||||||
responses.value[category.type] = response.data
|
responses.value[category.type] = response.data
|
||||||
} else if (category.type === 'series') {
|
} else if (category.type === 'series') {
|
||||||
|
@ -361,6 +389,7 @@ const search = async () => {
|
||||||
category.endpoint,
|
category.endpoint,
|
||||||
{ params }
|
{ params }
|
||||||
)
|
)
|
||||||
|
results.value.type = category.type
|
||||||
results.value.series = response.data.results
|
results.value.series = response.data.results
|
||||||
responses.value[category.type] = response.data
|
responses.value[category.type] = response.data
|
||||||
}
|
}
|
||||||
|
@ -378,20 +407,20 @@ const search = async () => {
|
||||||
const radioConfig = computed<RadioConfig | null>(() =>
|
const radioConfig = computed<RadioConfig | null>(() =>
|
||||||
count({ type: 'tags' }) > 0
|
count({ type: 'tags' }) > 0
|
||||||
? ({
|
? ({
|
||||||
type: 'tag',
|
type: 'tag',
|
||||||
names: resultsPerCategory({ type: 'tags' })
|
names: resultsPerCategory({ type: 'tags' })
|
||||||
.map((({ name }) => name))
|
.map((({ name }) => name))
|
||||||
})
|
})
|
||||||
: count({ type: 'playlists' }) > 0
|
: count({ type: 'playlists' }) > 0
|
||||||
? ({
|
? ({
|
||||||
type: 'playlist',
|
type: 'playlist',
|
||||||
ids: resultsPerCategory({ type: 'playlists' }).map(({ id }) => id.toString())
|
ids: resultsPerCategory({ type: 'playlists' }).map(({ id }) => id.toString())
|
||||||
})
|
})
|
||||||
: count({ type: 'artists' }) > 0
|
: count({ type: 'artists' }) > 0
|
||||||
? ({
|
? ({
|
||||||
type: 'artist',
|
type: 'artist',
|
||||||
ids: resultsPerCategory({ type: 'artists' }).map(({ id }) => id.toString())
|
ids: resultsPerCategory({ type: 'artists' }).map(({ id }) => id.toString())
|
||||||
})
|
})
|
||||||
: null
|
: null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -489,23 +518,50 @@ watch(queryDebounced, search, { immediate: true })
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- If response has "url": "webfinger://node1@node1.funkwhale.test" -> Link to go directly to the federation page -->
|
<template v-if="category.type === 'rss' && count(category) > 0">
|
||||||
|
<Alert
|
||||||
<span v-if="category.type === 'rss' && count(category) > 0">
|
blue
|
||||||
<Alert>{{ t('modals.search.tryAgain') }}</Alert>
|
style="grid-column: 1 / -1"
|
||||||
<Link
|
|
||||||
v-for="channel in resultsPerCategory(category)"
|
|
||||||
:key="channel.artist.fid"
|
|
||||||
:to="channel.artist.fid"
|
|
||||||
autofocus
|
|
||||||
>
|
>
|
||||||
{{ channel.artist.name }}
|
{{ t('modals.search.tryAgain') }}
|
||||||
</Link>
|
</Alert>
|
||||||
</span>
|
<channel-card
|
||||||
|
v-if="results.rss && results.rss[0]"
|
||||||
|
:key="results.rss[0].uuid"
|
||||||
|
:object="results.rss[0]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
<span v-else-if="category.type === 'federation'">
|
<span v-else-if="category.type === 'federation' && count(category) > 0">
|
||||||
<!-- TODO: Federation search: backend adapter + display, fix results_per_category query -->
|
<template
|
||||||
<!-- {{ resultsPerCategory(category) }} -->
|
v-for="result in resultsPerCategory(category)"
|
||||||
|
:key="result.id"
|
||||||
|
>
|
||||||
|
<ActorLink
|
||||||
|
v-if="result.object && result.type === 'account'"
|
||||||
|
:actor="result.object as components['schemas']['APIActor']"
|
||||||
|
/>
|
||||||
|
<ChannelCard
|
||||||
|
v-else-if="result.object && result.type === 'channel'"
|
||||||
|
:object="result.object as components['schemas']['Channel']"
|
||||||
|
/>
|
||||||
|
<ArtistCard
|
||||||
|
v-else-if="result.object && result.type === 'artist'"
|
||||||
|
:artist="result.object as components['schemas']['Artist']"
|
||||||
|
/>
|
||||||
|
<AlbumCard
|
||||||
|
v-else-if="result.object && result.type === 'album'"
|
||||||
|
:album="result.object as components['schemas']['Album']"
|
||||||
|
/>
|
||||||
|
<PlaylistCard
|
||||||
|
v-else-if="result.object && result.type === 'playlist'"
|
||||||
|
:playlist="result.object as components['schemas']['Playlist']"
|
||||||
|
/>
|
||||||
|
<TrackTable
|
||||||
|
v-else-if="result.object && result.type === 'track'"
|
||||||
|
:tracks="[result.object] as components['schemas']['Track'][]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<EmptyState
|
<EmptyState
|
||||||
|
|
Loading…
Reference in New Issue