diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 06194348d7e95b37e110479648f864070adcb131..b1283ea86d8a886cab3e98ca962fa7bdc4841c2e 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -1223,6 +1223,7 @@ VERSATILEIMAGEFIELD_RENDITION_KEY_SETS = { "attachment_square": [ ("original", "url"), ("medium_square_crop", "crop__200x200"), + ("large_square_crop", "crop__600x600"), ], } VERSATILEIMAGEFIELD_SETTINGS = { diff --git a/api/funkwhale_api/common/models.py b/api/funkwhale_api/common/models.py index e0bd216198f799d517c7820983ce464d0343ff74..688af540635b0ffebad4c2271a49ba76852da31e 100644 --- a/api/funkwhale_api/common/models.py +++ b/api/funkwhale_api/common/models.py @@ -267,6 +267,13 @@ class Attachment(models.Model): proxy_url = reverse("api:v1:attachments-proxy", kwargs={"uuid": self.uuid}) return federation_utils.full_url(proxy_url + "?next=medium_square_crop") + @property + def download_url_large_square_crop(self): + if self.file: + return utils.media_url(self.file.crop["600x600"].url) + proxy_url = reverse("api:v1:attachments-proxy", kwargs={"uuid": self.uuid}) + return federation_utils.full_url(proxy_url + "?next=large_square_crop") + class MutationAttachment(models.Model): """ diff --git a/api/funkwhale_api/common/serializers.py b/api/funkwhale_api/common/serializers.py index 9210c0603c3925d724dc82d79ba5e1c0fae227f8..fa194ea8aa138eb72a27dc62b514b321324d2e2a 100644 --- a/api/funkwhale_api/common/serializers.py +++ b/api/funkwhale_api/common/serializers.py @@ -297,6 +297,7 @@ class AttachmentSerializer(serializers.Serializer): urls["source"] = o.url urls["original"] = o.download_url_original urls["medium_square_crop"] = o.download_url_medium_square_crop + urls["large_square_crop"] = o.download_url_large_square_crop return urls def create(self, validated_data): diff --git a/api/funkwhale_api/common/views.py b/api/funkwhale_api/common/views.py index a4818acd96325c7e069e5646e48cfe25ebc0658c..fe669ad215a7a8f48a877f09c9aff2699989e3a0 100644 --- a/api/funkwhale_api/common/views.py +++ b/api/funkwhale_api/common/views.py @@ -175,7 +175,7 @@ class AttachmentViewSet( return r size = request.GET.get("next", "original").lower() - if size not in ["original", "medium_square_crop"]: + if size not in ["original", "medium_square_crop", "large_square_crop"]: size = "original" try: diff --git a/api/funkwhale_api/federation/tasks.py b/api/funkwhale_api/federation/tasks.py index 86bef50e1127a4c26c7ccc92af9b971961f04976..c2bdfad347bc9d35fda9025d18d657c1a4df3ac1 100644 --- a/api/funkwhale_api/federation/tasks.py +++ b/api/funkwhale_api/federation/tasks.py @@ -429,7 +429,7 @@ def fetch(fetch_obj): ) except Exception: logger.exception( - "Error while fetching actor outbox: %s", obj.actor.outbox.url + "Error while fetching actor outbox: %s", obj.actor.outbox_url ) else: if result.get("next_page"): diff --git a/api/tests/common/test_serializers.py b/api/tests/common/test_serializers.py index 1d23efb66140d01231a26745c6df4b84f617574b..e8ef04bca4eb988b84c787505c29b69d6c356d5d 100644 --- a/api/tests/common/test_serializers.py +++ b/api/tests/common/test_serializers.py @@ -200,6 +200,9 @@ def test_attachment_serializer_existing_file(factories, to_api_date): "medium_square_crop": federation_utils.full_url( attachment.file.crop["200x200"].url ), + "large_square_crop": federation_utils.full_url( + attachment.file.crop["600x600"].url + ), }, } @@ -227,6 +230,9 @@ def test_attachment_serializer_remote_file(factories, to_api_date): "medium_square_crop": federation_utils.full_url( proxy_url + "?next=medium_square_crop" ), + "large_square_crop": federation_utils.full_url( + proxy_url + "?next=large_square_crop" + ), }, } diff --git a/changes/changelog.d/1205.enhancement b/changes/changelog.d/1205.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..a3ccf4dc37e2915596cb16522b06ea606653f24f --- /dev/null +++ b/changes/changelog.d/1205.enhancement @@ -0,0 +1 @@ +Added a new, large thumbnail size for cover images (#1205 \ No newline at end of file diff --git a/changes/changelog.d/1210.enhancement b/changes/changelog.d/1210.enhancement new file mode 100644 index 0000000000000000000000000000000000000000..a41d714e51a337fea53c7a0b0a746a9308328ed7 --- /dev/null +++ b/changes/changelog.d/1210.enhancement @@ -0,0 +1 @@ +Enforce authentication when viewing remote channels, profiles and libraries (#1210) \ No newline at end of file diff --git a/front/src/components/Queue.vue b/front/src/components/Queue.vue index bc6979164fa41b088787c658aff20bc40276b5d3..59971c21e5f968b034e9d4391569393b9630119b 100644 --- a/front/src/components/Queue.vue +++ b/front/src/components/Queue.vue @@ -6,8 +6,8 @@ <div class="ui six wide column current-track"> <div class="ui basic segment" id="player"> <template v-if="currentTrack"> - <img ref="cover" alt="" v-if="currentTrack.cover && currentTrack.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.urls.original)"> - <img ref="cover" alt="" v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.urls.original)"> + <img ref="cover" alt="" v-if="currentTrack.cover && currentTrack.cover.urls.large_square_crop" :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.urls.large_square_crop)"> + <img ref="cover" alt="" v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.urls.large_square_crop" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.urls.large_square_crop)"> <img class="ui image" alt="" v-else src="../assets/audio/default-cover.png"> <h1 class="ui header"> <div class="content ellipsis"> diff --git a/front/src/components/library/AlbumDropdown.vue b/front/src/components/library/AlbumDropdown.vue index 07b7b5a7674fe67243424916b4c79dfbd077f6f9..3cc42f6efb7b7538768022bf0a2d737469ae70f3 100644 --- a/front/src/components/library/AlbumDropdown.vue +++ b/front/src/components/library/AlbumDropdown.vue @@ -20,6 +20,15 @@ <button class="ui floating dropdown circular icon basic button" :title="labels.more" v-dropdown="{direction: 'downward'}"> <i class="ellipsis vertical icon"></i> <div class="menu"> + <a + :href="object.fid" + v-if="domain != $store.getters['instance/domain']" + target="_blank" + class="basic item"> + <i class="external icon"></i> + <translate :translate-params="{domain: domain}" translate-context="Content/*/Button.Label/Verb">View on %{ domain }</translate> + </a> + <div role="button" v-if="isEmbedable" @@ -86,6 +95,7 @@ import EmbedWizard from "@/components/audio/EmbedWizard" import Modal from '@/components/semantic/Modal' import ReportMixin from '@/components/mixins/Report' +import {getDomain} from '@/utils' export default { mixins: [ReportMixin], @@ -108,6 +118,11 @@ export default { } }, computed: { + domain () { + if (this.object) { + return getDomain(this.object.fid) + } + }, labels() { return { more: this.$pgettext('*/*/Button.Label/Noun', "Moreā¦"), diff --git a/front/src/components/library/ArtistBase.vue b/front/src/components/library/ArtistBase.vue index 35b0eb02f662bf4d59efe78c984a0e326ae6e15a..f23b1311ed65b115b2462233b7791ef7b35a0b7b 100644 --- a/front/src/components/library/ArtistBase.vue +++ b/front/src/components/library/ArtistBase.vue @@ -57,6 +57,15 @@ <button class="ui floating dropdown icon button" ref="dropdown" v-dropdown> <i class="dropdown icon"></i> <div class="menu"> + <a + :href="object.fid" + v-if="domain != $store.getters['instance/domain']" + target="_blank" + class="basic item"> + <i class="external icon"></i> + <translate :translate-params="{domain: domain}" translate-context="Content/*/Button.Label/Verb">View on %{ domain }</translate> + </a> + <button role="button" v-if="publicLibraries.length > 0" @@ -137,6 +146,8 @@ import RadioButton from "@/components/radios/Button" import TagsList from "@/components/tags/List" import ReportMixin from '@/components/mixins/Report' +import {getDomain} from '@/utils' + const FETCH_URL = "albums/" export default { @@ -205,6 +216,11 @@ export default { } }, computed: { + domain () { + if (this.object) { + return getDomain(this.object.fid) + } + }, isPlayable() { return ( this.object.albums.filter(a => { diff --git a/front/src/components/library/TrackBase.vue b/front/src/components/library/TrackBase.vue index cf9a93659845098b60d37c7441876b916f970372..18c2ff5e0f476013018415d2cf3316af2ad3dc4c 100644 --- a/front/src/components/library/TrackBase.vue +++ b/front/src/components/library/TrackBase.vue @@ -44,6 +44,14 @@ <button class="ui floating dropdown circular icon basic button" :title="labels.more" v-dropdown="{direction: 'downward'}"> <i class="ellipsis vertical icon"></i> <div class="menu" style="right: 0; left: auto"> + <a + :href="track.fid" + v-if="domain != $store.getters['instance/domain']" + target="_blank" + class="basic item"> + <i class="external icon"></i> + <translate :translate-params="{domain: domain}" translate-context="Content/*/Button.Label/Verb">View on %{ domain }</translate> + </a> <div role="button" v-if="publicLibraries.length > 0" @@ -116,6 +124,7 @@ import time from "@/utils/time" import axios from "axios" import url from "@/utils/url" +import {getDomain} from '@/utils' import logger from "@/logging" import PlayButton from "@/components/audio/PlayButton" import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon" @@ -190,6 +199,11 @@ export default { } }, computed: { + domain () { + if (this.track) { + return getDomain(this.track.fid) + } + }, publicLibraries () { return this.libraries.filter(l => { return l.privacy_level === 'everyone' diff --git a/front/src/utils.js b/front/src/utils.js index fb7117a20aeaca00dba89006aa0afb1493d8caf5..596ec07d53bdd33a11804e8e2345a453cf5ce284 100644 --- a/front/src/utils.js +++ b/front/src/utils.js @@ -51,3 +51,9 @@ export function checkRedirectToLogin (store, router) { router.push({name: 'login', query: {next: router.currentRoute.fullPath}}) } } + +export function getDomain (url) { + let parser = document.createElement("a") + parser.href = url + return parser.hostname +} \ No newline at end of file diff --git a/front/src/views/auth/ProfileBase.vue b/front/src/views/auth/ProfileBase.vue index 353ab1581c5f7bc5a474b659d5671ece13218730..f776322bf14c1f0590178a774e7812f6d3d46663 100644 --- a/front/src/views/auth/ProfileBase.vue +++ b/front/src/views/auth/ProfileBase.vue @@ -9,6 +9,14 @@ <button class="ui pointing dropdown icon small basic right floated button" ref="dropdown" v-dropdown="{direction: 'downward'}" style="position: absolute; right: 1em; top: 1em;"> <i class="ellipsis vertical icon"></i> <div class="menu"> + <a + :href="object.fid" + v-if="object.domain != $store.getters['instance/domain']" + target="_blank" + class="basic item"> + <i class="external icon"></i> + <translate :translate-params="{domain: object.domain}" translate-context="Content/*/Button.Label/Verb">View on %{ domain }</translate> + </a> <div role="button" class="basic item" @@ -93,7 +101,12 @@ export default { } }, created() { - this.fetch() + let authenticated = this.$store.state.auth.authenticated + if (!authenticated && this.domain && this.$store.getters['instance/domain'] != this.domain) { + this.$router.push({name: 'login', query: {next: this.$route.fullPath}}) + } else { + this.fetch() + } }, beforeRouteUpdate (to, from, next) { to.meta.preserveScrollPosition = true diff --git a/front/src/views/channels/DetailBase.vue b/front/src/views/channels/DetailBase.vue index be8481dafbf6637ca33242e78944f265f1eecf10..7d7dc2143b4779a39cf5256315fad7c3aa04e4d5 100644 --- a/front/src/views/channels/DetailBase.vue +++ b/front/src/views/channels/DetailBase.vue @@ -84,6 +84,14 @@ <i class="code icon"></i> <translate translate-context="Content/*/Button.Label/Verb">Embed</translate> </a> + <a + :href="object.url" + v-if="object.actor.domain != $store.getters['instance/domain']" + target="_blank" + class="basic item"> + <i class="external icon"></i> + <translate :translate-params="{domain: object.actor.domain}" translate-context="Content/*/Button.Label/Verb">View on %{ domain }</translate> + </a> <div class="divider"></div> <a href="" @@ -270,6 +278,10 @@ export default { }, async created() { await this.fetchData() + let authenticated = this.$store.state.auth.authenticated + if (!authenticated && this.$store.getters['instance/domain'] != this.object.actor.domain) { + this.$router.push({name: 'login', query: {next: this.$route.fullPath}}) + } }, methods: { async fetchData() { diff --git a/front/src/views/library/DetailBase.vue b/front/src/views/library/DetailBase.vue index a58a179be6a402687806b46fc3a8045736267755..16d2fc56c23b200e45100db1d738f48784fd1b1e 100644 --- a/front/src/views/library/DetailBase.vue +++ b/front/src/views/library/DetailBase.vue @@ -7,6 +7,14 @@ <button class="ui pointing dropdown icon small basic right floated button" ref="dropdown" v-dropdown="{direction: 'downward'}" style="position: absolute; right: 1em; top: 1em;"> <i class="ellipsis vertical icon"></i> <div class="menu"> + <a + :href="object.fid" + v-if="object.actor.domain != $store.getters['instance/domain']" + target="_blank" + class="basic item"> + <i class="external icon"></i> + <translate :translate-params="{domain: object.actor.domain}" translate-context="Content/*/Button.Label/Verb">View on %{ domain }</translate> + </a> <div role="button" class="basic item" @@ -148,6 +156,10 @@ export default { }, async created() { await this.fetchData() + let authenticated = this.$store.state.auth.authenticated + if (!authenticated && this.$store.getters['instance/domain'] != this.object.actor.domain) { + this.$router.push({name: 'login', query: {next: this.$route.fullPath}}) + } }, methods: { async fetchData() {