Merge branch 'master' into develop
This commit is contained in:
commit
b95710bb3a
|
@ -1,5 +1,6 @@
|
|||
import html
|
||||
import requests
|
||||
import xml.sax.saxutils
|
||||
|
||||
from django import http
|
||||
from django.conf import settings
|
||||
|
@ -51,7 +52,13 @@ def serve_spa(request):
|
|||
|
||||
# let's inject our meta tags in the HTML
|
||||
head += "\n" + "\n".join(render_tags(final_tags)) + "\n</head>"
|
||||
|
||||
css = get_custom_css() or ""
|
||||
if css:
|
||||
# We add the style add the end of the body to ensure it has the highest
|
||||
# priority (since it will come after other stylesheets)
|
||||
body, tail = tail.split("</body>", 1)
|
||||
css = "<style>{}</style>".format(css)
|
||||
tail = body + "\n" + css + "\n</body>" + tail
|
||||
return http.HttpResponse(head + tail)
|
||||
|
||||
|
||||
|
@ -128,6 +135,14 @@ def get_request_head_tags(request):
|
|||
return match.func(request, *match.args, **match.kwargs)
|
||||
|
||||
|
||||
def get_custom_css():
|
||||
css = preferences.get("ui__custom_css").strip()
|
||||
if not css:
|
||||
return
|
||||
|
||||
return xml.sax.saxutils.escape(css)
|
||||
|
||||
|
||||
class SPAFallbackMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
|
|
@ -4,6 +4,7 @@ from dynamic_preferences.registries import global_preferences_registry
|
|||
|
||||
raven = types.Section("raven")
|
||||
instance = types.Section("instance")
|
||||
ui = types.Section("ui")
|
||||
|
||||
|
||||
@global_preferences_registry.register
|
||||
|
@ -98,3 +99,19 @@ class InstanceNodeinfoStatsEnabled(types.BooleanPreference):
|
|||
"Disable this if you don't want to share usage and library statistics "
|
||||
"in the nodeinfo endpoint but don't want to disable it completely."
|
||||
)
|
||||
|
||||
|
||||
@global_preferences_registry.register
|
||||
class CustomCSS(types.StringPreference):
|
||||
show_in_api = True
|
||||
section = ui
|
||||
name = "custom_css"
|
||||
verbose_name = "Custom CSS code"
|
||||
default = ""
|
||||
help_text = (
|
||||
"Custom CSS code, to be included in a <style> tag on all pages. "
|
||||
"Loading third-party resources such as fonts or images can affect the performance "
|
||||
"of the app and the privacy of your users."
|
||||
)
|
||||
widget = widgets.Textarea
|
||||
field_kwargs = {"required": False}
|
||||
|
|
|
@ -256,7 +256,7 @@ class SubsonicViewSet(viewsets.GenericViewSet):
|
|||
if max_bitrate:
|
||||
max_bitrate = max_bitrate * 1000
|
||||
|
||||
format = data.get("format", "raw") or None
|
||||
format = data.get("format") or None
|
||||
if max_bitrate and not format:
|
||||
# specific bitrate requested, but no format specified
|
||||
# so we use a default one, cf #867. This helps with clients
|
||||
|
|
|
@ -141,3 +141,47 @@ def test_get_route_head_tags(mocker, settings):
|
|||
assert tags == match.func.return_value
|
||||
match.func.assert_called_once_with(request, *[], **{"pk": 42})
|
||||
resolve.assert_called_once_with(request.path, urlconf=settings.SPA_URLCONF)
|
||||
|
||||
|
||||
def test_serve_spa_includes_custom_css(mocker, no_api_auth):
|
||||
request = mocker.Mock(path="/")
|
||||
mocker.patch.object(
|
||||
middleware,
|
||||
"get_spa_html",
|
||||
return_value="<html><head></head><body></body></html>",
|
||||
)
|
||||
mocker.patch.object(middleware, "get_default_head_tags", return_value=[])
|
||||
mocker.patch.object(middleware, "get_request_head_tags", return_value=[])
|
||||
get_custom_css = mocker.patch.object(
|
||||
middleware, "get_custom_css", return_value="body { background: black; }"
|
||||
)
|
||||
response = middleware.serve_spa(request)
|
||||
|
||||
assert response.status_code == 200
|
||||
expected = [
|
||||
"<html><head>\n\n</head><body>",
|
||||
"<style>body { background: black; }</style>",
|
||||
"</body></html>",
|
||||
]
|
||||
get_custom_css.assert_called_once_with()
|
||||
assert response.content == "\n".join(expected).encode()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"custom_css, expected",
|
||||
[
|
||||
("body { background: black; }", "body { background: black; }"),
|
||||
(
|
||||
"body { injection: </style> & Hello",
|
||||
"body { injection: </style> & Hello",
|
||||
),
|
||||
(
|
||||
'body { background: url("image/url"); }',
|
||||
'body { background: url("image/url"); }',
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_get_custom_css(preferences, custom_css, expected):
|
||||
preferences["ui__custom_css"] = custom_css
|
||||
|
||||
assert middleware.get_custom_css() == expected
|
||||
|
|
|
@ -29,8 +29,8 @@ from rest_framework.test import APIClient, APIRequestFactory
|
|||
|
||||
from funkwhale_api.activity import record
|
||||
from funkwhale_api.federation import actors
|
||||
from funkwhale_api.music import licenses
|
||||
from funkwhale_api.moderation import mrf
|
||||
from funkwhale_api.music import licenses
|
||||
|
||||
|
||||
pytest_plugins = "aiohttp.pytest_plugin"
|
||||
|
|
|
@ -288,15 +288,16 @@ def test_stream_transcode(
|
|||
mocker,
|
||||
settings,
|
||||
):
|
||||
upload = factories["music.Upload"](playable=True)
|
||||
params = {"id": upload.track.pk, "maxBitRate": max_bitrate}
|
||||
if format:
|
||||
params["format"] = format
|
||||
settings.SUBSONIC_DEFAULT_TRANSCODING_FORMAT = default_transcoding_format
|
||||
url = reverse("api:subsonic-stream")
|
||||
mocked_serve = mocker.patch.object(
|
||||
music_views, "handle_serve", return_value=Response()
|
||||
)
|
||||
upload = factories["music.Upload"](playable=True)
|
||||
response = logged_in_api_client.get(
|
||||
url, {"id": upload.track.pk, "maxBitRate": max_bitrate, "format": format}
|
||||
)
|
||||
response = logged_in_api_client.get(url, params)
|
||||
|
||||
mocked_serve.assert_called_once_with(
|
||||
upload=upload,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Fixed remaining transcoding issue with Subsonic API (#867)
|
|
@ -0,0 +1 @@
|
|||
Admins can now add custom CSS from their pod settings (#879)
|
|
@ -364,7 +364,11 @@ export default {
|
|||
return
|
||||
}
|
||||
let image = this.$refs.cover
|
||||
this.ambiantColors = ColorThief.prototype.getPalette(image, 4).slice(0, 4)
|
||||
try {
|
||||
this.ambiantColors = ColorThief.prototype.getPalette(image, 4).slice(0, 4)
|
||||
} catch (e) {
|
||||
console.log('Cannot generate player background from cover image, likely a cross-origin tainted canvas issue')
|
||||
}
|
||||
},
|
||||
handleError({ sound, error }) {
|
||||
this.$store.commit("player/isLoadingAudio", false)
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<tbody>
|
||||
<tr v-for="track in tracks">
|
||||
<td class="play-cell">
|
||||
<play-button :class="['basic', {orange: isPlaying && track.id === currentTrack.id}, 'icon']" :discrete="true" :track="track"></play-button>
|
||||
<play-button :class="['basic', {orange: currentTrack && isPlaying && track.id === currentTrack.id}, 'icon']" :discrete="true" :track="track"></play-button>
|
||||
</td>
|
||||
<td class="content-cell" colspan="5">
|
||||
<track-favorite-icon :track="track"></track-favorite-icon>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<tr>
|
||||
<td>
|
||||
<play-button :class="['basic', {orange: isPlaying && track.id === currentTrack.id}, 'icon']" :discrete="true" :is-playable="playable" :track="track"></play-button>
|
||||
<play-button :class="['basic', {orange: currentTrack && isPlaying && track.id === currentTrack.id}, 'icon']" :discrete="true" :is-playable="playable" :track="track"></play-button>
|
||||
</td>
|
||||
<td>
|
||||
<img class="ui mini image" v-if="track.album.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](track.album.cover.small_square_crop)">
|
||||
|
|
|
@ -85,6 +85,7 @@ export default {
|
|||
let moderationLabel = this.$pgettext('Content/Admin/Menu', 'Moderation')
|
||||
let subsonicLabel = this.$pgettext('Content/Admin/Menu', 'Subsonic')
|
||||
let statisticsLabel = this.$pgettext('Content/Admin/Menu', 'Statistics')
|
||||
let uiLabel = this.$pgettext('Content/Admin/Menu', 'User Interface')
|
||||
let errorLabel = this.$pgettext('Content/Admin/Menu', 'Error reporting')
|
||||
return [
|
||||
{
|
||||
|
@ -143,6 +144,11 @@ export default {
|
|||
id: "subsonic",
|
||||
settings: ["subsonic__enabled"]
|
||||
},
|
||||
{
|
||||
label: uiLabel,
|
||||
id: "ui",
|
||||
settings: ["ui__custom_css"]
|
||||
},
|
||||
{
|
||||
label: statisticsLabel,
|
||||
id: "statistics",
|
||||
|
|
Loading…
Reference in New Issue