Merge branch 'release/0.16.1'
This commit is contained in:
commit
0913b716e5
2
.env.dev
2
.env.dev
|
@ -7,7 +7,7 @@ C_FORCE_ROOT=true
|
|||
FUNKWHALE_HOSTNAME=localhost
|
||||
FUNKWHALE_PROTOCOL=http
|
||||
PYTHONDONTWRITEBYTECODE=true
|
||||
WEBPACK_DEVSERVER_PORT=8080
|
||||
VUE_PORT=8080
|
||||
MUSIC_DIRECTORY_PATH=/music
|
||||
BROWSABLE_API_ENABLED=True
|
||||
FORWARDED_PROTO=http
|
||||
|
|
|
@ -72,16 +72,17 @@ api/media
|
|||
api/staticfiles
|
||||
api/static
|
||||
api/.pytest_cache
|
||||
|
||||
api/celerybeat-*
|
||||
# Front
|
||||
oldfront/node_modules/
|
||||
front/static/translations
|
||||
front/node_modules/
|
||||
front/dist/
|
||||
front/npm-debug.log*
|
||||
front/yarn-debug.log*
|
||||
front/yarn-error.log*
|
||||
front/test/unit/coverage
|
||||
front/test/e2e/reports
|
||||
front/tests/unit/coverage
|
||||
front/tests/e2e/reports
|
||||
front/selenium-debug.log
|
||||
docs/_build
|
||||
|
||||
|
|
|
@ -156,7 +156,6 @@ test_api:
|
|||
tags:
|
||||
- docker
|
||||
|
||||
|
||||
test_front:
|
||||
stage: test
|
||||
image: node:9
|
||||
|
@ -166,7 +165,7 @@ test_front:
|
|||
- branches
|
||||
script:
|
||||
- yarn install
|
||||
- yarn run unit
|
||||
- yarn test:unit
|
||||
cache:
|
||||
key: "funkwhale__front_dependencies"
|
||||
paths:
|
||||
|
@ -179,7 +178,6 @@ test_front:
|
|||
tags:
|
||||
- docker
|
||||
|
||||
|
||||
build_front:
|
||||
stage: build
|
||||
image: node:9
|
||||
|
@ -192,8 +190,8 @@ build_front:
|
|||
- yarn run i18n-compile
|
||||
# this is to ensure we don't have any errors in the output,
|
||||
# cf https://code.eliotberriot.com/funkwhale/funkwhale/issues/169
|
||||
- yarn run build | tee /dev/stderr | (! grep -i 'ERROR in')
|
||||
- chmod -R 750 dist
|
||||
- yarn build | tee /dev/stderr | (! grep -i 'ERROR in')
|
||||
- chmod -R 755 dist
|
||||
cache:
|
||||
key: "funkwhale__front_dependencies"
|
||||
paths:
|
||||
|
@ -210,7 +208,6 @@ build_front:
|
|||
tags:
|
||||
- docker
|
||||
|
||||
|
||||
pages:
|
||||
stage: test
|
||||
image: python:3.6
|
||||
|
|
79
CHANGELOG
79
CHANGELOG
|
@ -10,7 +10,82 @@ This changelog is viewable on the web at https://docs.funkwhale.audio/changelog.
|
|||
|
||||
.. towncrier
|
||||
|
||||
0.16 (unreleased)
|
||||
0.16.1 (2018-08-19)
|
||||
-------------------
|
||||
|
||||
Upgrade instructions are available at
|
||||
https://docs.funkwhale.audio/upgrading.html
|
||||
|
||||
Features:
|
||||
|
||||
- Make funkwhale themable by loading external stylesheets (#456)
|
||||
|
||||
Enhancements:
|
||||
|
||||
- Add link to admin on "Staff member" button (#202)
|
||||
- Can now add a description to radios and better radio cards (#331)
|
||||
- Display track duration in track tables (#461)
|
||||
- More permissive default permissions for front-end files (#388)
|
||||
- Simpler configuration and toolchain for the front-end using vue-cli (!375)
|
||||
- Use Howler to manage audio instead of our own dirty/untested code (#392)
|
||||
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Fix alignment issue on top bar in Admin tabs (#395)
|
||||
- Fix Apache2 permission issue preventing `/media` folder from being served
|
||||
correctly (#389)
|
||||
- Fix loading on browse page lists causing them to go down, and dimming over
|
||||
the top bar (#468)
|
||||
- Fixed (again): administration section not showing up in sidebar after login
|
||||
(#245)
|
||||
- Fixed audio mimetype not showing up on track detail and list (#459)
|
||||
- Fixed broken audio playback on Chrome and invisible volume control (#390)
|
||||
- Fixed broken federation import on big imports due to missing transaction
|
||||
logic (#397)
|
||||
- Fixed crash on artist pages when no cover is available (#457)
|
||||
- Fixed favorited status of tracks not appearing in interface (#398)
|
||||
- Fixed invitation code not prefilled in form when accessing invitation link
|
||||
(#476)
|
||||
- Fixed typos in scheduled tasks configuration (#487)
|
||||
- Removed release date error in case of empty date (#478)
|
||||
- Removed white on white artist button on hover, on Album page (#393)
|
||||
- Smarter date parsing during import by replacing arrow with pendulum (#376)
|
||||
- Display public playlists properly for anonymous users (#488)
|
||||
|
||||
|
||||
i18n:
|
||||
|
||||
- Added portuguese, spanish and german translations
|
||||
|
||||
|
||||
Custom themes for Funkwhale
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If you ever wanted to give a custom look and feel to your instance, this is now possible.
|
||||
|
||||
Check https://docs.funkwhale.audio/configuration.html#theming if you want to know more!
|
||||
|
||||
|
||||
Fix Apache2 configuration file for media block [Manual action required]
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The permission scope on the current Apache2 configuration file is too narrow, preventing thumbnails from being served.
|
||||
|
||||
On Apache2 setups, you have to replace the following line::
|
||||
|
||||
<Directory /srv/funkwhale/data/media/albums>
|
||||
|
||||
with::
|
||||
|
||||
<Directory /srv/funkwhale/data/media>
|
||||
|
||||
You can now restart your server::
|
||||
|
||||
sudo systemctl restart apache2
|
||||
|
||||
|
||||
0.16 (2018-07-22)
|
||||
-----------------
|
||||
|
||||
Upgrade instructions are available at
|
||||
|
@ -232,7 +307,7 @@ In the end, the ``volumes`` directives of your containers should look like that:
|
|||
|
||||
|
||||
Removed Cacheops dependency
|
||||
---------------------------
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
We removed one of our dependency named django-cacheops. It was unly used in a few places,
|
||||
and not playing nice with other dependencies.
|
||||
|
|
|
@ -23,7 +23,7 @@ As the front-end can work with any Funkwhale server, you can work with the front
|
|||
and make it talk with an existing instance (like the demo one, or you own instance, if you have one).
|
||||
|
||||
If even that is too much for you, you can also make your changes without any development environment,
|
||||
and open a merge request. We will be able to to review your work easily by spawning automatically a
|
||||
and open a merge request. We will be able to review your work easily by spawning automatically a
|
||||
live version of your changes, thanks to Gitlab Review apps.
|
||||
|
||||
Setup front-end only development environment
|
||||
|
@ -43,7 +43,7 @@ Setup front-end only development environment
|
|||
4. Launch the development server::
|
||||
|
||||
# this will serve the front-end on http://localhost:8000
|
||||
WEBPACK_DEVSERVER_PORT=8000 yarn dev
|
||||
VUE_PORT=8000 yarn serve
|
||||
|
||||
5. Make the front-end talk with an existing server (like https://demo.funkwhale.audio),
|
||||
by clicking on the corresponding link in the footer
|
||||
|
@ -264,7 +264,7 @@ When working on federation with traefik, ensure you have this in your ``env``::
|
|||
|
||||
# This will ensure we don't bind any port on the host, and thus enable
|
||||
# multiple instances of funkwhale to be spawned concurrently.
|
||||
WEBPACK_DEVSERVER_PORT_BINDING=
|
||||
VUE_PORT_BINDING=
|
||||
# This disable certificate verification
|
||||
EXTERNAL_REQUESTS_VERIFY_SSL=false
|
||||
# this ensure you don't have incorrect urls pointing to http resources
|
||||
|
@ -466,12 +466,12 @@ Running tests
|
|||
|
||||
To run the front-end test suite, use the following command::
|
||||
|
||||
docker-compose -f dev.yml run --rm front yarn run unit
|
||||
docker-compose -f dev.yml run --rm front yarn test:unit
|
||||
|
||||
We also support a "watch and test" mode were we continually relaunch
|
||||
tests when changes are recorded on the file system::
|
||||
|
||||
docker-compose -f dev.yml run --rm front yarn run unit-watch
|
||||
docker-compose -f dev.yml run --rm front yarn test:unit -w
|
||||
|
||||
The latter is especially useful when you are debugging failing tests.
|
||||
|
|
@ -26,7 +26,7 @@ Contribute
|
|||
----------
|
||||
|
||||
Contribution guidelines as well as development installation instructions
|
||||
are outlined in `CONTRIBUTING <CONTRIBUTING>`_.
|
||||
are outlined in `CONTRIBUTING <CONTRIBUTING.rst>`_.
|
||||
|
||||
Translate
|
||||
^^^^^^^^^
|
||||
|
|
|
@ -344,9 +344,9 @@ CELERY_BROKER_URL = env(
|
|||
# Your common stuff: Below this line define 3rd party library settings
|
||||
CELERY_TASK_DEFAULT_RATE_LIMIT = 1
|
||||
CELERY_TASK_TIME_LIMIT = 300
|
||||
CELERYBEAT_SCHEDULE = {
|
||||
CELERY_BEAT_SCHEDULE = {
|
||||
"federation.clean_music_cache": {
|
||||
"task": "funkwhale_api.federation.tasks.clean_music_cache",
|
||||
"task": "federation.clean_music_cache",
|
||||
"schedule": crontab(hour="*/2"),
|
||||
"options": {"expires": 60 * 2},
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
__version__ = "0.16"
|
||||
__version__ = "0.16.1"
|
||||
__version_info__ = tuple(
|
||||
[
|
||||
int(num) if num.isdigit() else num
|
||||
|
|
|
@ -796,6 +796,8 @@ class LibraryTrackActionSerializer(common_serializers.ActionSerializer):
|
|||
jobs.append(job)
|
||||
|
||||
music_models.ImportJob.objects.bulk_create(jobs)
|
||||
music_tasks.import_batch_run.delay(import_batch_id=batch.pk)
|
||||
funkwhale_utils.on_commit(
|
||||
music_tasks.import_batch_run.delay, import_batch_id=batch.pk
|
||||
)
|
||||
|
||||
return {"batch": {"id": batch.pk}}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import arrow
|
||||
import datetime
|
||||
import mutagen
|
||||
import pendulum
|
||||
from django import forms
|
||||
|
||||
NODEFAULT = object()
|
||||
|
@ -101,6 +102,11 @@ class FirstUUIDField(forms.UUIDField):
|
|||
return super().to_python(value)
|
||||
|
||||
|
||||
def get_date(value):
|
||||
parsed = pendulum.parse(str(value))
|
||||
return datetime.date(parsed.year, parsed.month, parsed.day)
|
||||
|
||||
|
||||
VALIDATION = {
|
||||
"musicbrainz_artistid": FirstUUIDField(),
|
||||
"musicbrainz_albumid": FirstUUIDField(),
|
||||
|
@ -118,7 +124,7 @@ CONF = {
|
|||
"title": {},
|
||||
"artist": {},
|
||||
"album": {},
|
||||
"date": {"field": "date", "to_application": lambda v: arrow.get(v).date()},
|
||||
"date": {"field": "date", "to_application": get_date},
|
||||
"musicbrainz_albumid": {},
|
||||
"musicbrainz_artistid": {},
|
||||
"musicbrainz_recordingid": {"field": "musicbrainz_trackid"},
|
||||
|
@ -134,7 +140,7 @@ CONF = {
|
|||
"title": {},
|
||||
"artist": {},
|
||||
"album": {},
|
||||
"date": {"field": "date", "to_application": lambda v: arrow.get(v).date()},
|
||||
"date": {"field": "date", "to_application": get_date},
|
||||
"musicbrainz_albumid": {"field": "MusicBrainz Album Id"},
|
||||
"musicbrainz_artistid": {"field": "MusicBrainz Artist Id"},
|
||||
"musicbrainz_recordingid": {"field": "MusicBrainz Track Id"},
|
||||
|
@ -148,10 +154,7 @@ CONF = {
|
|||
"title": {"field": "TIT2"},
|
||||
"artist": {"field": "TPE1"},
|
||||
"album": {"field": "TALB"},
|
||||
"date": {
|
||||
"field": "TDRC",
|
||||
"to_application": lambda v: arrow.get(str(v)).date(),
|
||||
},
|
||||
"date": {"field": "TDRC", "to_application": get_date},
|
||||
"musicbrainz_albumid": {"field": "MusicBrainz Album Id"},
|
||||
"musicbrainz_artistid": {"field": "MusicBrainz Artist Id"},
|
||||
"musicbrainz_recordingid": {
|
||||
|
@ -172,10 +175,7 @@ CONF = {
|
|||
"title": {},
|
||||
"artist": {},
|
||||
"album": {},
|
||||
"date": {
|
||||
"field": "date",
|
||||
"to_application": lambda v: arrow.get(str(v)).date(),
|
||||
},
|
||||
"date": {"field": "date", "to_application": get_date},
|
||||
"musicbrainz_albumid": {},
|
||||
"musicbrainz_artistid": {},
|
||||
"musicbrainz_recordingid": {"field": "musicbrainz_trackid"},
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import datetime
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import uuid
|
||||
|
||||
import arrow
|
||||
import markdown
|
||||
import pendulum
|
||||
from django.conf import settings
|
||||
from django.core.files import File
|
||||
from django.core.files.base import ContentFile
|
||||
|
@ -125,9 +124,7 @@ def import_artist(v):
|
|||
|
||||
|
||||
def parse_date(v):
|
||||
if len(v) == 4:
|
||||
return datetime.date(int(v), 1, 1)
|
||||
d = arrow.get(v).date()
|
||||
d = pendulum.parse(v).date()
|
||||
return d
|
||||
|
||||
|
||||
|
@ -147,7 +144,7 @@ class AlbumQuerySet(models.QuerySet):
|
|||
class Album(APIModelMixin):
|
||||
title = models.CharField(max_length=255)
|
||||
artist = models.ForeignKey(Artist, related_name="albums", on_delete=models.CASCADE)
|
||||
release_date = models.DateField(null=True)
|
||||
release_date = models.DateField(null=True, blank=True)
|
||||
release_group_id = models.UUIDField(null=True, blank=True)
|
||||
cover = VersatileImageField(
|
||||
upload_to="albums/covers/%Y/%m/%d", null=True, blank=True
|
||||
|
|
|
@ -35,7 +35,7 @@ djangorestframework>=3.7,<3.8
|
|||
djangorestframework-jwt>=1.11,<1.12
|
||||
oauth2client<4
|
||||
google-api-python-client>=1.6,<1.7
|
||||
arrow>=0.12,<0.13
|
||||
pendulum>=2,<3
|
||||
persisting-theory>=0.2,<0.3
|
||||
django-versatileimagefield>=1.9,<1.10
|
||||
django-filter>=1.1,<1.2
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import arrow
|
||||
import pendulum
|
||||
import pytest
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
|
@ -455,7 +455,7 @@ def test_library_actor_handle_create_audio(mocker, factories):
|
|||
assert lt.title == a["metadata"]["recording"]["title"]
|
||||
assert lt.artist_name == a["metadata"]["artist"]["name"]
|
||||
assert lt.album_title == a["metadata"]["release"]["title"]
|
||||
assert lt.published_date == arrow.get(a["published"])
|
||||
assert lt.published_date == pendulum.parse(a["published"])
|
||||
|
||||
|
||||
def test_library_actor_handle_create_audio_autoimport(mocker, factories):
|
||||
|
@ -494,7 +494,7 @@ def test_library_actor_handle_create_audio_autoimport(mocker, factories):
|
|||
assert lt.title == a["metadata"]["recording"]["title"]
|
||||
assert lt.artist_name == a["metadata"]["artist"]["name"]
|
||||
assert lt.album_title == a["metadata"]["release"]["title"]
|
||||
assert lt.published_date == arrow.get(a["published"])
|
||||
assert lt.published_date == pendulum.parse(a["published"])
|
||||
|
||||
batch = music_models.ImportBatch.objects.latest("id")
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import arrow
|
||||
import pendulum
|
||||
import pytest
|
||||
from django.core.paginator import Paginator
|
||||
|
||||
|
@ -492,7 +492,7 @@ def test_activity_pub_audio_serializer_to_library_track(factories):
|
|||
assert lt.title == audio["metadata"]["recording"]["title"]
|
||||
assert lt.artist_name == audio["metadata"]["artist"]["name"]
|
||||
assert lt.album_title == audio["metadata"]["release"]["title"]
|
||||
assert lt.published_date == arrow.get(audio["published"])
|
||||
assert lt.published_date == pendulum.parse(audio["published"])
|
||||
|
||||
|
||||
def test_activity_pub_audio_serializer_to_library_track_no_duplicate(factories):
|
||||
|
|
|
@ -12,6 +12,7 @@ from funkwhale_api.federation import (
|
|||
views,
|
||||
webfinger,
|
||||
)
|
||||
from funkwhale_api.music import tasks as music_tasks
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -398,7 +399,7 @@ def test_library_track_action_import(factories, superuser_api_client, mocker):
|
|||
lt2 = factories["federation.LibraryTrack"](library=lt1.library)
|
||||
lt3 = factories["federation.LibraryTrack"]()
|
||||
factories["federation.LibraryTrack"](library=lt3.library)
|
||||
mocked_run = mocker.patch("funkwhale_api.music.tasks.import_batch_run.delay")
|
||||
mocked_run = mocker.patch("funkwhale_api.common.utils.on_commit")
|
||||
|
||||
payload = {
|
||||
"objects": "all",
|
||||
|
@ -416,7 +417,9 @@ def test_library_track_action_import(factories, superuser_api_client, mocker):
|
|||
assert batch.jobs.count() == 2
|
||||
for i, job in enumerate(batch.jobs.all()):
|
||||
assert job.library_track == imported_lts[i]
|
||||
mocked_run.assert_called_once_with(import_batch_id=batch.pk)
|
||||
mocked_run.assert_called_once_with(
|
||||
music_tasks.import_batch_run.delay, import_batch_id=batch.pk
|
||||
)
|
||||
|
||||
|
||||
def test_local_actor_detail(factories, api_client):
|
||||
|
|
|
@ -122,3 +122,11 @@ def test_mbid_clean_keeps_only_first(field_name):
|
|||
result = field.to_python("/".join([u1, u2]))
|
||||
|
||||
assert str(result) == u1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"raw,expected",
|
||||
[("2017", datetime.date(2017, 1, 1)), ("2017-12-31", datetime.date(2017, 12, 31))],
|
||||
)
|
||||
def test_date_parsing(raw, expected):
|
||||
assert metadata.get_date(raw) == expected
|
||||
|
|
|
@ -111,7 +111,7 @@ Define MUSIC_DIRECTORY_PATH /srv/funkwhale/data/music
|
|||
Require all granted
|
||||
</Directory>
|
||||
|
||||
<Directory /srv/funkwhale/data/media/albums>
|
||||
<Directory /srv/funkwhale/data/media>
|
||||
Options FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
|
|
34
dev.yml
34
dev.yml
|
@ -1,4 +1,4 @@
|
|||
version: '3'
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
front:
|
||||
|
@ -8,22 +8,22 @@ services:
|
|||
- .env
|
||||
environment:
|
||||
- "HOST=0.0.0.0"
|
||||
- "WEBPACK_DEVSERVER_PORT=${WEBPACK_DEVSERVER_PORT-8080}"
|
||||
- "VUE_PORT=${VUE_PORT-8080}"
|
||||
ports:
|
||||
- "${WEBPACK_DEVSERVER_PORT_BINDING-8080:}${WEBPACK_DEVSERVER_PORT-8080}"
|
||||
- "${VUE_PORT_BINDING-8080:}${VUE_PORT-8080}"
|
||||
volumes:
|
||||
- './front:/app'
|
||||
- '/app/node_modules'
|
||||
- './po:/po'
|
||||
- "./front:/app"
|
||||
- "/app/node_modules"
|
||||
- "./po:/po"
|
||||
networks:
|
||||
- federation
|
||||
- internal
|
||||
labels:
|
||||
traefik.backend: "${COMPOSE_PROJECT_NAME-node1}"
|
||||
traefik.frontend.rule: "Host:${COMPOSE_PROJECT_NAME-node1}.funkwhale.test,${NODE_IP-127.0.0.1}"
|
||||
traefik.enable: 'true'
|
||||
traefik.federation.protocol: 'http'
|
||||
traefik.federation.port: "${WEBPACK_DEVSERVER_PORT-8080}"
|
||||
traefik.enable: "true"
|
||||
traefik.federation.protocol: "http"
|
||||
traefik.federation.port: "${VUE_PORT-8080}"
|
||||
|
||||
postgres:
|
||||
env_file:
|
||||
|
@ -53,9 +53,9 @@ services:
|
|||
context: ./api
|
||||
dockerfile: docker/Dockerfile.test
|
||||
links:
|
||||
- postgres
|
||||
- redis
|
||||
command: celery -A funkwhale_api.taskapp worker -l debug
|
||||
- postgres
|
||||
- redis
|
||||
command: celery -A funkwhale_api.taskapp worker -l debug -B
|
||||
environment:
|
||||
- "FUNKWHALE_HOSTNAME=${FUNKWHALE_HOSTNAME-localhost}"
|
||||
- "FUNKWHALE_HOSTNAME_SUFFIX=funkwhale.test"
|
||||
|
@ -98,7 +98,7 @@ services:
|
|||
- .env
|
||||
image: nginx
|
||||
environment:
|
||||
- "WEBPACK_DEVSERVER_PORT=${WEBPACK_DEVSERVER_PORT-8080}"
|
||||
- "VUE_PORT=${VUE_PORT-8080}"
|
||||
- "COMPOSE_PROJECT_NAME=${COMPOSE_PROJECT_NAME- }"
|
||||
- "FUNKWHALE_HOSTNAME=${FUNKWHALE_HOSTNAME-localhost}"
|
||||
links:
|
||||
|
@ -120,20 +120,20 @@ services:
|
|||
volumes:
|
||||
- ".:/app/"
|
||||
ports:
|
||||
- '35730:35730'
|
||||
- '8001:8001'
|
||||
- "35730:35730"
|
||||
- "8001:8001"
|
||||
|
||||
api-docs:
|
||||
image: swaggerapi/swagger-ui
|
||||
environment:
|
||||
- "API_URL=/swagger.yml"
|
||||
ports:
|
||||
- '8002:8080'
|
||||
- "8002:8080"
|
||||
volumes:
|
||||
- "./docs/swagger.yml:/usr/share/nginx/html/swagger.yml"
|
||||
|
||||
networks:
|
||||
internal:
|
||||
? internal
|
||||
federation:
|
||||
external:
|
||||
name: federation
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#!/bin/bash -eux
|
||||
|
||||
FORWARDED_PORT="$WEBPACK_DEVSERVER_PORT"
|
||||
FORWARDED_PORT="$VUE_PORT"
|
||||
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME// /}"
|
||||
if [ -n "$COMPOSE_PROJECT_NAME" ]; then
|
||||
echo
|
||||
|
|
|
@ -158,3 +158,79 @@ permissions are:
|
|||
There is no dedicated interface to manage users permissions, but superusers
|
||||
can login on the Django's admin at ``/api/admin/`` and grant permissions
|
||||
to users at ``/api/admin/users/user/``.
|
||||
|
||||
Theming
|
||||
-------
|
||||
|
||||
Funkwhale supports custom themes, which are great if you want to personnalize the
|
||||
look and feel of your instance. Theming is achieved by declaring
|
||||
additionnal stylesheets you want to load in the front-end.
|
||||
|
||||
Customize the settings
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In order to know what stylesheets to load, the front-end requests the following
|
||||
url: ``https://your.instance/settings.json``. On typical deployments, this url
|
||||
returns a 404 error, which is simply ignored.
|
||||
|
||||
However, if you return the appropriate payload on this url, you can make the magic
|
||||
work. We will store the necessary files in the ``/srv/funkwhale/custom`` directory:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
cd /srv/funkwhale/
|
||||
mkdir custom
|
||||
cat <<EOF > custom/settings.json
|
||||
{
|
||||
"additionalStylesheets": ["/custom/custom.css"]
|
||||
}
|
||||
EOF
|
||||
cat <<EOF > custom/custom.css
|
||||
body {
|
||||
background-color: red;
|
||||
}
|
||||
EOF
|
||||
|
||||
By executing the previous commands, you will end up with two files in your ``/srv/funkwhale/custom``
|
||||
directory:
|
||||
|
||||
- ``settings.json`` will tell the front-end what stylesheets you want to load (``/custom/custom.css`` in this example)
|
||||
- ``custom.css`` will hold your custom CSS
|
||||
|
||||
The last step to make this work is to ensure both files are served by the reverse proxy.
|
||||
|
||||
On nginx, add the following snippet to your vhost config::
|
||||
|
||||
location /settings.json {
|
||||
alias /srv/funkwhale/custom/settings.json;
|
||||
}
|
||||
location /custom {
|
||||
alias /srv/funkwhale/custom;
|
||||
}
|
||||
|
||||
On apache, use the following one::
|
||||
|
||||
Alias /settings.json /srv/funkwhale/custom/settings.json
|
||||
Alias /custom /srv/funkwhale/custom
|
||||
|
||||
<Directory "/srv/funkwhale/custom">
|
||||
Options FollowSymLinks
|
||||
AllowOverride None
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
Once done, reload your reverse proxy, refresh Funkwhale in your web browser, and you should see
|
||||
a red background.
|
||||
|
||||
.. note::
|
||||
|
||||
You can reference external urls as well in ``settings.json``, simply use
|
||||
the full urls. Be especially careful with external urls as they may affect your users
|
||||
privacy.
|
||||
|
||||
.. warning::
|
||||
|
||||
Loading additional stylesheets and CSS rules can affect the performance and
|
||||
usability of your instance. If you encounter issues with the interfaces and use
|
||||
custom stylesheets, try to disable those to ensure the issue is not caused
|
||||
by your customizations.
|
||||
|
|
|
@ -1 +1 @@
|
|||
.. include:: ../CONTRIBUTING
|
||||
.. include:: ../CONTRIBUTING.rst
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"presets": [
|
||||
["env", { "modules": false }],
|
||||
"stage-2"
|
||||
],
|
||||
"plugins": ["transform-runtime"],
|
||||
"comments": false,
|
||||
"env": {
|
||||
"test": {
|
||||
"presets": ["env", "stage-2"],
|
||||
"plugins": [ "istanbul" ]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
build/*.js
|
||||
config/*.js
|
|
@ -1,27 +0,0 @@
|
|||
// http://eslint.org/docs/user-guide/configuring
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
},
|
||||
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
|
||||
extends: 'standard',
|
||||
// required to lint *.vue files
|
||||
plugins: [
|
||||
'html'
|
||||
],
|
||||
// add your custom rules here
|
||||
'rules': {
|
||||
// allow paren-less arrow functions
|
||||
'arrow-parens': 0,
|
||||
// allow async-await
|
||||
'generator-star-spacing': 0,
|
||||
// allow debugger during development
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
|
@ -1,8 +0,0 @@
|
|||
// https://github.com/michael-ciniawsky/postcss-load-config
|
||||
|
||||
module.exports = {
|
||||
"plugins": {
|
||||
// to edit target browsers: use "browserlist" field in package.json
|
||||
"autoprefixer": {}
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@ RUN curl -L -o /usr/local/bin/jq https://github.com/stedolan/jq/releases/downloa
|
|||
|
||||
EXPOSE 8080
|
||||
WORKDIR /app/
|
||||
ADD package.json .
|
||||
ADD package.json yarn.lock ./
|
||||
RUN yarn install
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["npm", "run", "dev"]
|
||||
CMD ["yarn", "serve"]
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
require('./check-versions')()
|
||||
|
||||
process.env.NODE_ENV = 'production'
|
||||
|
||||
var ora = require('ora')
|
||||
var rm = require('rimraf')
|
||||
var path = require('path')
|
||||
var chalk = require('chalk')
|
||||
var webpack = require('webpack')
|
||||
var config = require('../config')
|
||||
var webpackConfig = require('./webpack.prod.conf')
|
||||
|
||||
var spinner = ora('building for production...')
|
||||
spinner.start()
|
||||
|
||||
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
|
||||
if (err) throw err
|
||||
webpack(webpackConfig, function (err, stats) {
|
||||
spinner.stop()
|
||||
if (err) throw err
|
||||
process.stdout.write(stats.toString({
|
||||
colors: true,
|
||||
modules: false,
|
||||
children: false,
|
||||
chunks: false,
|
||||
chunkModules: false
|
||||
}) + '\n\n')
|
||||
|
||||
console.log(chalk.cyan(' Build complete.\n'))
|
||||
console.log(chalk.yellow(
|
||||
' Tip: built files are meant to be served over an HTTP server.\n' +
|
||||
' Opening index.html over file:// won\'t work.\n'
|
||||
))
|
||||
})
|
||||
})
|
|
@ -1,48 +0,0 @@
|
|||
var chalk = require('chalk')
|
||||
var semver = require('semver')
|
||||
var packageConfig = require('../package.json')
|
||||
var shell = require('shelljs')
|
||||
function exec (cmd) {
|
||||
return require('child_process').execSync(cmd).toString().trim()
|
||||
}
|
||||
|
||||
var versionRequirements = [
|
||||
{
|
||||
name: 'node',
|
||||
currentVersion: semver.clean(process.version),
|
||||
versionRequirement: packageConfig.engines.node
|
||||
},
|
||||
]
|
||||
|
||||
if (shell.which('npm')) {
|
||||
versionRequirements.push({
|
||||
name: 'npm',
|
||||
currentVersion: exec('npm --version'),
|
||||
versionRequirement: packageConfig.engines.npm
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = function () {
|
||||
var warnings = []
|
||||
for (var i = 0; i < versionRequirements.length; i++) {
|
||||
var mod = versionRequirements[i]
|
||||
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
|
||||
warnings.push(mod.name + ': ' +
|
||||
chalk.red(mod.currentVersion) + ' should be ' +
|
||||
chalk.green(mod.versionRequirement)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (warnings.length) {
|
||||
console.log('')
|
||||
console.log(chalk.yellow('To use this template, you must update following to modules:'))
|
||||
console.log()
|
||||
for (var i = 0; i < warnings.length; i++) {
|
||||
var warning = warnings[i]
|
||||
console.log(' ' + warning)
|
||||
}
|
||||
console.log()
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
/* eslint-disable */
|
||||
require('eventsource-polyfill')
|
||||
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
|
||||
|
||||
hotClient.subscribe(function (event) {
|
||||
if (event.action === 'reload') {
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
|
@ -1,92 +0,0 @@
|
|||
require('./check-versions')()
|
||||
|
||||
var config = require('../config')
|
||||
if (!process.env.NODE_ENV) {
|
||||
process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
|
||||
}
|
||||
|
||||
var opn = require('opn')
|
||||
var path = require('path')
|
||||
var express = require('express')
|
||||
var webpack = require('webpack')
|
||||
var proxyMiddleware = require('http-proxy-middleware')
|
||||
var webpackConfig = process.env.NODE_ENV === 'testing'
|
||||
? require('./webpack.prod.conf')
|
||||
: require('./webpack.dev.conf')
|
||||
|
||||
// default port where dev server listens for incoming traffic
|
||||
var port = process.env.PORT || config.dev.port
|
||||
var host = process.env.HOST || config.dev.host
|
||||
// automatically open browser, if not set will be false
|
||||
var autoOpenBrowser = !!config.dev.autoOpenBrowser
|
||||
// Define HTTP proxies to your custom API backend
|
||||
// https://github.com/chimurai/http-proxy-middleware
|
||||
var proxyTable = config.dev.proxyTable
|
||||
|
||||
var app = express()
|
||||
var compiler = webpack(webpackConfig)
|
||||
|
||||
var devMiddleware = require('webpack-dev-middleware')(compiler, {
|
||||
publicPath: webpackConfig.output.publicPath,
|
||||
quiet: true
|
||||
})
|
||||
|
||||
var hotMiddleware = require('webpack-hot-middleware')(compiler, {
|
||||
log: () => {}
|
||||
})
|
||||
// force page reload when html-webpack-plugin template changes
|
||||
compiler.plugin('compilation', function (compilation) {
|
||||
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
|
||||
hotMiddleware.publish({ action: 'reload' })
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
||||
// proxy api requests
|
||||
Object.keys(proxyTable).forEach(function (context) {
|
||||
var options = proxyTable[context]
|
||||
if (typeof options === 'string') {
|
||||
options = { target: options }
|
||||
}
|
||||
app.use(proxyMiddleware(options.filter || context, options))
|
||||
})
|
||||
|
||||
// handle fallback for HTML5 history API
|
||||
app.use(require('connect-history-api-fallback')())
|
||||
|
||||
// serve webpack bundle output
|
||||
app.use(devMiddleware)
|
||||
|
||||
// enable hot-reload and state-preserving
|
||||
// compilation error display
|
||||
app.use(hotMiddleware)
|
||||
|
||||
// serve pure static assets
|
||||
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
|
||||
app.use(staticPath, express.static('./static'))
|
||||
|
||||
var uri = 'http://' + host + ':' + port
|
||||
|
||||
var _resolve
|
||||
var readyPromise = new Promise(resolve => {
|
||||
_resolve = resolve
|
||||
})
|
||||
|
||||
console.log('> Starting dev server...')
|
||||
devMiddleware.waitUntilValid(() => {
|
||||
console.log('> Listening at ' + uri + '\n')
|
||||
// when env is testing, don't need open it
|
||||
if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
|
||||
opn(uri)
|
||||
}
|
||||
_resolve()
|
||||
})
|
||||
|
||||
var server = app.listen(port, host)
|
||||
|
||||
module.exports = {
|
||||
ready: readyPromise,
|
||||
close: () => {
|
||||
server.close()
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
var path = require('path')
|
||||
var config = require('../config')
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
|
||||
exports.assetsPath = function (_path) {
|
||||
var assetsSubDirectory = process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsSubDirectory
|
||||
: config.dev.assetsSubDirectory
|
||||
return path.posix.join(assetsSubDirectory, _path)
|
||||
}
|
||||
|
||||
exports.cssLoaders = function (options) {
|
||||
options = options || {}
|
||||
|
||||
var cssLoader = {
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
minimize: process.env.NODE_ENV === 'production',
|
||||
sourceMap: options.sourceMap
|
||||
}
|
||||
}
|
||||
|
||||
// generate loader string to be used with extract text plugin
|
||||
function generateLoaders (loader, loaderOptions) {
|
||||
var loaders = [cssLoader]
|
||||
if (loader) {
|
||||
loaders.push({
|
||||
loader: loader + '-loader',
|
||||
options: Object.assign({}, loaderOptions, {
|
||||
sourceMap: options.sourceMap
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Extract CSS when that option is specified
|
||||
// (which is the case during production build)
|
||||
if (options.extract) {
|
||||
return ExtractTextPlugin.extract({
|
||||
use: loaders,
|
||||
fallback: 'vue-style-loader'
|
||||
})
|
||||
} else {
|
||||
return ['vue-style-loader'].concat(loaders)
|
||||
}
|
||||
}
|
||||
|
||||
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
|
||||
return {
|
||||
css: generateLoaders(),
|
||||
postcss: generateLoaders(),
|
||||
less: generateLoaders('less'),
|
||||
sass: generateLoaders('sass', { indentedSyntax: true }),
|
||||
scss: generateLoaders('sass'),
|
||||
stylus: generateLoaders('stylus'),
|
||||
styl: generateLoaders('stylus')
|
||||
}
|
||||
}
|
||||
|
||||
// Generate loaders for standalone style files (outside of .vue)
|
||||
exports.styleLoaders = function (options) {
|
||||
var output = []
|
||||
var loaders = exports.cssLoaders(options)
|
||||
for (var extension in loaders) {
|
||||
var loader = loaders[extension]
|
||||
output.push({
|
||||
test: new RegExp('\\.' + extension + '$'),
|
||||
use: loader
|
||||
})
|
||||
}
|
||||
return output
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
var utils = require('./utils')
|
||||
var config = require('../config')
|
||||
var isProduction = process.env.NODE_ENV === 'production'
|
||||
|
||||
module.exports = {
|
||||
loaders: utils.cssLoaders({
|
||||
sourceMap: isProduction
|
||||
? config.build.productionSourceMap
|
||||
: config.dev.cssSourceMap,
|
||||
extract: isProduction
|
||||
})
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
var path = require('path')
|
||||
var utils = require('./utils')
|
||||
var config = require('../config')
|
||||
var vueLoaderConfig = require('./vue-loader.conf')
|
||||
|
||||
function resolve (dir) {
|
||||
return path.join(__dirname, '..', dir)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
app: './src/main.js'
|
||||
},
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: '[name].js',
|
||||
publicPath: process.env.NODE_ENV === 'production'
|
||||
? config.build.assetsPublicPath
|
||||
: config.dev.assetsPublicPath
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.vue', '.json'],
|
||||
alias: {
|
||||
'vue$': 'vue/dist/vue.esm.js',
|
||||
'@': resolve('src')
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|vue)$/,
|
||||
loader: 'eslint-loader',
|
||||
enforce: 'pre',
|
||||
include: [resolve('src'), resolve('test')],
|
||||
options: {
|
||||
formatter: require('eslint-friendly-formatter')
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: vueLoaderConfig
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
include: [resolve('src'), resolve('test')]
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('img/[name].[hash:7].[ext]')
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
var utils = require('./utils')
|
||||
var webpack = require('webpack')
|
||||
var config = require('../config')
|
||||
var merge = require('webpack-merge')
|
||||
var baseWebpackConfig = require('./webpack.base.conf')
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
|
||||
|
||||
// add hot-reload related code to entry chunks
|
||||
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
|
||||
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
|
||||
})
|
||||
|
||||
module.exports = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
|
||||
},
|
||||
// cheap-module-eval-source-map is faster for development
|
||||
devtool: '#cheap-module-eval-source-map',
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': config.dev.env
|
||||
}),
|
||||
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin(),
|
||||
// https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: 'index.html',
|
||||
inject: true
|
||||
}),
|
||||
new FriendlyErrorsPlugin()
|
||||
]
|
||||
})
|
|
@ -1,124 +0,0 @@
|
|||
var path = require('path')
|
||||
var utils = require('./utils')
|
||||
var webpack = require('webpack')
|
||||
var config = require('../config')
|
||||
var merge = require('webpack-merge')
|
||||
var baseWebpackConfig = require('./webpack.base.conf')
|
||||
var CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
var ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
|
||||
|
||||
var env = process.env.NODE_ENV === 'testing'
|
||||
? require('../config/test.env')
|
||||
: config.build.env
|
||||
|
||||
var webpackConfig = merge(baseWebpackConfig, {
|
||||
module: {
|
||||
rules: utils.styleLoaders({
|
||||
sourceMap: config.build.productionSourceMap,
|
||||
extract: true
|
||||
})
|
||||
},
|
||||
devtool: config.build.productionSourceMap ? '#source-map' : false,
|
||||
output: {
|
||||
path: config.build.assetsRoot,
|
||||
filename: utils.assetsPath('js/[name].[chunkhash].js'),
|
||||
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
|
||||
},
|
||||
plugins: [
|
||||
// http://vuejs.github.io/vue-loader/en/workflow/production.html
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': env
|
||||
}),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
warnings: false
|
||||
},
|
||||
sourceMap: true
|
||||
}),
|
||||
// extract css into its own file
|
||||
new ExtractTextPlugin({
|
||||
filename: utils.assetsPath('css/[name].[contenthash].css')
|
||||
}),
|
||||
// Compress extracted CSS. We are using this plugin so that possible
|
||||
// duplicated CSS from different components can be deduped.
|
||||
new OptimizeCSSPlugin({
|
||||
cssProcessorOptions: {
|
||||
safe: true
|
||||
}
|
||||
}),
|
||||
// generate dist index.html with correct asset hash for caching.
|
||||
// you can customize output by editing /index.html
|
||||
// see https://github.com/ampedandwired/html-webpack-plugin
|
||||
new HtmlWebpackPlugin({
|
||||
filename: process.env.NODE_ENV === 'testing'
|
||||
? 'index.html'
|
||||
: config.build.index,
|
||||
template: 'index.html',
|
||||
inject: true,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true
|
||||
// more options:
|
||||
// https://github.com/kangax/html-minifier#options-quick-reference
|
||||
},
|
||||
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
|
||||
chunksSortMode: 'dependency'
|
||||
}),
|
||||
// split vendor js into its own file
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'vendor',
|
||||
minChunks: function (module, count) {
|
||||
// any required modules inside node_modules are extracted to vendor
|
||||
return (
|
||||
module.resource &&
|
||||
/\.js$/.test(module.resource) &&
|
||||
module.resource.indexOf(
|
||||
path.join(__dirname, '../node_modules')
|
||||
) === 0
|
||||
)
|
||||
}
|
||||
}),
|
||||
// extract webpack runtime and module manifest to its own file in order to
|
||||
// prevent vendor hash from being updated whenever app bundle is updated
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'manifest',
|
||||
chunks: ['vendor']
|
||||
}),
|
||||
// copy custom static assets
|
||||
new CopyWebpackPlugin([
|
||||
{
|
||||
from: path.resolve(__dirname, '../static'),
|
||||
to: config.build.assetsSubDirectory,
|
||||
ignore: ['.*']
|
||||
}
|
||||
])
|
||||
]
|
||||
})
|
||||
|
||||
if (config.build.productionGzip) {
|
||||
var CompressionWebpackPlugin = require('compression-webpack-plugin')
|
||||
|
||||
webpackConfig.plugins.push(
|
||||
new CompressionWebpackPlugin({
|
||||
asset: '[path].gz[query]',
|
||||
algorithm: 'gzip',
|
||||
test: new RegExp(
|
||||
'\\.(' +
|
||||
config.build.productionGzipExtensions.join('|') +
|
||||
')$'
|
||||
),
|
||||
threshold: 10240,
|
||||
minRatio: 0.8
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (config.build.bundleAnalyzerReport) {
|
||||
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
|
||||
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
|
||||
}
|
||||
|
||||
module.exports = webpackConfig
|
|
@ -1,31 +0,0 @@
|
|||
// This is the webpack config used for unit tests.
|
||||
|
||||
var utils = require('./utils')
|
||||
var webpack = require('webpack')
|
||||
var merge = require('webpack-merge')
|
||||
var baseConfig = require('./webpack.base.conf')
|
||||
|
||||
var webpackConfig = merge(baseConfig, {
|
||||
// use inline sourcemap for karma-sourcemap-loader
|
||||
module: {
|
||||
rules: utils.styleLoaders()
|
||||
},
|
||||
devtool: '#inline-source-map',
|
||||
resolveLoader: {
|
||||
alias: {
|
||||
// necessary to to make lang="scss" work in test when using vue-loader's ?inject option
|
||||
// see discussion at https://github.com/vuejs/vue-loader/issues/724
|
||||
'scss-loader': 'sass-loader'
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': require('../config/test.env')
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
// no need for app entry during tests
|
||||
delete webpackConfig.entry
|
||||
|
||||
module.exports = webpackConfig
|
|
@ -1,6 +0,0 @@
|
|||
var merge = require('webpack-merge')
|
||||
var prodEnv = require('./prod.env')
|
||||
|
||||
module.exports = merge(prodEnv, {
|
||||
NODE_ENV: '"development"'
|
||||
})
|
|
@ -1,65 +0,0 @@
|
|||
// see http://vuejs-templates.github.io/webpack for documentation.
|
||||
var path = require('path')
|
||||
|
||||
module.exports = {
|
||||
build: {
|
||||
env: require('./prod.env'),
|
||||
index: path.resolve(__dirname, '../dist/index.html'),
|
||||
assetsRoot: path.resolve(__dirname, '../dist'),
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '/',
|
||||
productionSourceMap: false,
|
||||
// Gzip off by default as many popular static hosts such as
|
||||
// Surge or Netlify already gzip all static assets for you.
|
||||
// Before setting to `true`, make sure to:
|
||||
// npm install --save-dev compression-webpack-plugin
|
||||
productionGzip: false,
|
||||
productionGzipExtensions: ['js', 'css'],
|
||||
// Run the build command with an extra argument to
|
||||
// View the bundle analyzer report after build finishes:
|
||||
// `npm run build --report`
|
||||
// Set to `true` or `false` to always turn it on or off
|
||||
bundleAnalyzerReport: process.env.npm_config_report
|
||||
},
|
||||
dev: {
|
||||
env: require('./dev.env'),
|
||||
port: parseInt(process.env.WEBPACK_DEVSERVER_PORT),
|
||||
host: '127.0.0.1',
|
||||
autoOpenBrowser: true,
|
||||
assetsSubDirectory: 'static',
|
||||
assetsPublicPath: '/',
|
||||
proxyTable: {
|
||||
'**': {
|
||||
target: 'http://nginx:6001',
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
filter: function (pathname, req) {
|
||||
let proxified = ['rest', '.well-known', 'staticfiles', 'media', 'federation', 'api']
|
||||
let matches = proxified.filter(e => {
|
||||
return pathname.match(`^/${e}`)
|
||||
})
|
||||
return matches.length > 0
|
||||
}
|
||||
},
|
||||
// '/.well-known': {
|
||||
// target: 'http://nginx:6001',
|
||||
// changeOrigin: true
|
||||
// },
|
||||
// '/media': {
|
||||
// target: 'http://nginx:6001',
|
||||
// changeOrigin: true,
|
||||
// },
|
||||
// '/staticfiles': {
|
||||
// target: 'http://nginx:6001',
|
||||
// changeOrigin: true,
|
||||
// },
|
||||
|
||||
},
|
||||
// CSS Sourcemaps off by default because relative paths are "buggy"
|
||||
// with this option, according to the CSS-Loader README
|
||||
// (https://github.com/webpack/css-loader#sourcemaps)
|
||||
// In our experience, they generally work as expected,
|
||||
// just be aware of this issue when enabling this option.
|
||||
cssSourceMap: false
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
let url = process.env.INSTANCE_URL || '/'
|
||||
module.exports = {
|
||||
NODE_ENV: '"production"',
|
||||
INSTANCE_URL: `"${url}"`
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
var merge = require('webpack-merge')
|
||||
var devEnv = require('./dev.env')
|
||||
|
||||
module.exports = merge(devEnv, {
|
||||
NODE_ENV: '"testing"'
|
||||
})
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Funkwhale</title>
|
||||
<link rel="shortcut icon" type="image/png" href="/static/favicon.png"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
|
@ -3,7 +3,7 @@ msgstr ""
|
|||
"Project-Id-Version: Arabic (FunkWhale)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-07-22 14:12+0200\n"
|
||||
"PO-Revision-Date: 2018-07-22 15:50+0000\n"
|
||||
"PO-Revision-Date: 2018-07-24 19:50+0000\n"
|
||||
"Last-Translator: ButterflyOfFire <butterflyoffire+funkwhale@protonmail.com>\n"
|
||||
"Language-Team: Arabic <https://translate.funkwhale.audio/projects/funkwhale/"
|
||||
"funkwhale/front/ar/>\n"
|
||||
|
@ -81,19 +81,19 @@ msgstr[5] "%{ count } مَقاطِع"
|
|||
|
||||
#: front/src/components/common/Duration.vue:2
|
||||
msgid "%{ hours } h %{ minutes } min"
|
||||
msgstr ""
|
||||
msgstr "%{ hours } سا %{ minutes } د"
|
||||
|
||||
#: front/src/components/common/Duration.vue:5
|
||||
msgid "%{ minutes } min"
|
||||
msgstr ""
|
||||
msgstr "%{ minutes } د"
|
||||
|
||||
#: front/src/components/activity/Like.vue:7
|
||||
msgid "%{ user } favorited a track"
|
||||
msgstr ""
|
||||
msgstr "أُعجِب %{ user } بمقطع"
|
||||
|
||||
#: front/src/components/activity/Listen.vue:7
|
||||
msgid "%{ user } listened to a track"
|
||||
msgstr ""
|
||||
msgstr "قام %{ user } بالاستماع إلى مَقطَع"
|
||||
|
||||
#: front/src/components/auth/Profile.vue:49
|
||||
msgid "%{ username }'s profile"
|
||||
|
@ -131,9 +131,8 @@ msgstr[4] "%{ count } مَقاطِع"
|
|||
msgstr[5] "%{ count } مَقاطِع"
|
||||
|
||||
#: front/src/components/About.vue:5
|
||||
#, fuzzy
|
||||
msgid "About %{ instance }"
|
||||
msgstr "عن مثيل الخادوم هذا"
|
||||
msgstr "عن %{ instance }"
|
||||
|
||||
#: front/src/App.vue:54
|
||||
msgid "About Funkwhale"
|
||||
|
@ -146,11 +145,11 @@ msgstr "عن مثيل الخادوم هذا"
|
|||
#: front/src/components/manage/library/RequestsTable.vue:28
|
||||
#: front/src/components/manage/library/RequestsTable.vue:62
|
||||
msgid "Accepted"
|
||||
msgstr ""
|
||||
msgstr "تم قبوله"
|
||||
|
||||
#: front/src/components/auth/SubsonicTokenForm.vue:111
|
||||
msgid "Access disabled"
|
||||
msgstr ""
|
||||
msgstr "عُطّل النفاذ"
|
||||
|
||||
#: front/src/components/Home.vue:109
|
||||
msgid "Access your music from a clean interface that focus on what really matters"
|
||||
|
@ -502,7 +501,7 @@ msgstr ""
|
|||
|
||||
#: front/src/components/auth/Settings.vue:98
|
||||
msgid "Changing your password will have the following consequences"
|
||||
msgstr ""
|
||||
msgstr "سوف ينجرّ ما يلي عند تعديل كلمتك السرية"
|
||||
|
||||
#: front/src/App.vue:4
|
||||
msgid "Choose your instance"
|
||||
|
@ -615,9 +614,8 @@ msgid "Creation date"
|
|||
msgstr "تاريخ الإنشاء"
|
||||
|
||||
#: front/src/components/auth/Settings.vue:54
|
||||
#, fuzzy
|
||||
msgid "Current avatar"
|
||||
msgstr "المَقطَع الحالي"
|
||||
msgstr "الصورة الرمزية الحالية"
|
||||
|
||||
#: front/src/components/playlists/PlaylistModal.vue:8
|
||||
msgid "Current track"
|
||||
|
@ -701,9 +699,8 @@ msgid "Do you want to delete the playlist \"%{ playlist }\"?"
|
|||
msgstr "متأكّد مِن أنك تريد حذف قائمة المَقاطِع الموسيقية \"%{ playlist }\" ؟"
|
||||
|
||||
#: front/src/views/radios/Detail.vue:26
|
||||
#, fuzzy
|
||||
msgid "Do you want to delete the radio \"%{ radio }\"?"
|
||||
msgstr "أتريد حقا حذف إذاعة \"{{ radio }}\" ؟"
|
||||
msgstr "أتريد حقا حذف إذاعة \"%{ radio }\" ؟"
|
||||
|
||||
#: front/src/components/common/ActionTable.vue:29
|
||||
msgid "Do you want to launch %{ action } on %{ count } element?"
|
||||
|
@ -908,7 +905,7 @@ msgstr "الفديرالية"
|
|||
|
||||
#: front/src/views/federation/LibraryDetail.vue:3
|
||||
msgid "File mirroring"
|
||||
msgstr ""
|
||||
msgstr "النسخ المتماثل للملفات"
|
||||
|
||||
#: front/src/components/library/import/FileUpload.vue:43
|
||||
msgid "File name"
|
||||
|
@ -1037,7 +1034,7 @@ msgstr "جلب البيانات الوصفية ذات الصّلة"
|
|||
|
||||
#: front/src/App.vue:74
|
||||
msgid "Help us translate Funkwhale"
|
||||
msgstr ""
|
||||
msgstr "ساعدنا على ترجمة فانك وايل"
|
||||
|
||||
#: front/src/components/library/Home.vue:65
|
||||
msgid "Home"
|
||||
|
@ -1192,9 +1189,8 @@ msgid "Instance information"
|
|||
msgstr "معلومات عن مثيل الخادوم"
|
||||
|
||||
#: front/src/components/library/Radios.vue:9
|
||||
#, fuzzy
|
||||
msgid "Instance radios"
|
||||
msgstr "معلومات عن مثيل الخادوم"
|
||||
msgstr "إذاعات مثيل الخادوم"
|
||||
|
||||
#: front/src/views/admin/Settings.vue:75
|
||||
msgid "Instance settings"
|
||||
|
@ -1395,6 +1391,13 @@ msgid ""
|
|||
" </a>\n"
|
||||
" project, which you can think about as the Wikipedia of music."
|
||||
msgstr ""
|
||||
"البيانات الوصفية هي الم البيانات المتعلقة بالموسيقى التي تريد استيرادها. و "
|
||||
"هي تحتوي على معلومات عن الفنانين و الألبومات و المَقاطِع الموسيقية. و بغرض "
|
||||
"إنشاء مكتبة بها جودة، يُستحسن جلب البيانات مِن \n"
|
||||
" <a href=\"https://musicbrainz.org\" target=\"_blank\">\n"
|
||||
" MusicBrainz\n"
|
||||
" </a>\n"
|
||||
" مشروع بمثابة ويكيبيديا للموسيقى."
|
||||
|
||||
#: front/src/components/Sidebar.vue:48
|
||||
#: src/components/library/import/Main.vue:18
|
||||
|
@ -1498,6 +1501,8 @@ msgstr "الكلمة السرية الجديدة"
|
|||
#: front/src/components/library/import/FileUpload.vue:36
|
||||
msgid "Once all your files are uploaded, simply click the following button to check the import status."
|
||||
msgstr ""
|
||||
"حينما تتم عملية إرسال كافة ملفاتك، إضغط على الزر التالي للتحقق مِن حالة "
|
||||
"الإستيراد."
|
||||
|
||||
#: front/src/components/federation/LibraryCard.vue:21
|
||||
#: front/src/components/manage/users/InvitationsTable.vue:20
|
||||
|
@ -1683,6 +1688,8 @@ msgstr "الرجاء التأكّد مِن صحة اسم المستخدِم و
|
|||
#: front/src/components/auth/Settings.vue:46
|
||||
msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px."
|
||||
msgstr ""
|
||||
"نسق PNG أو GIF أو JPG. الحجم الأقصى 2 ميغابيت. سيتم تغيير حجمها إلى 400×400 "
|
||||
"بكسل."
|
||||
|
||||
#: front/src/components/library/import/Main.vue:26
|
||||
msgid "Previous step"
|
||||
|
@ -1722,18 +1729,16 @@ msgid "Radio Builder"
|
|||
msgstr "مُنشِئ الإذاعات و الراديو"
|
||||
|
||||
#: front/src/components/library/radios/Builder.vue:15
|
||||
#, fuzzy
|
||||
msgid "Radio created"
|
||||
msgstr "إسم الإذاعة"
|
||||
msgstr "تم إنشاء الإذاعة"
|
||||
|
||||
#: front/src/components/library/radios/Builder.vue:21
|
||||
msgid "Radio name"
|
||||
msgstr "إسم الإذاعة"
|
||||
|
||||
#: front/src/components/library/radios/Builder.vue:12
|
||||
#, fuzzy
|
||||
msgid "Radio updated"
|
||||
msgstr "إسم الإذاعة"
|
||||
msgstr "تم تحديث الإذاعة"
|
||||
|
||||
#: front/src/components/library/Library.vue:10
|
||||
#: src/components/library/Radios.vue:141
|
||||
|
@ -1746,12 +1751,11 @@ msgstr "أحدث الأنشطة على مثيل الخادوم هذا"
|
|||
|
||||
#: front/src/components/library/Home.vue:24
|
||||
msgid "Recently added"
|
||||
msgstr ""
|
||||
msgstr "تمت إضافتها مؤخرا"
|
||||
|
||||
#: front/src/components/library/Home.vue:11
|
||||
#, fuzzy
|
||||
msgid "Recently favorited"
|
||||
msgstr "في المفضلة"
|
||||
msgstr "تمت إضافتها إلى المفضلة حديثا"
|
||||
|
||||
#: front/src/components/library/Home.vue:6
|
||||
msgid "Recently listened"
|
||||
|
@ -1787,9 +1791,8 @@ msgid "Remove"
|
|||
msgstr "حذف"
|
||||
|
||||
#: front/src/components/auth/Settings.vue:58
|
||||
#, fuzzy
|
||||
msgid "Remove avatar"
|
||||
msgstr "حذف"
|
||||
msgstr "حذف الصورة الرمزية"
|
||||
|
||||
#: front/src/components/favorites/TrackFavoriteIcon.vue:19
|
||||
msgid "Remove from favorites"
|
||||
|
@ -2257,9 +2260,8 @@ msgid "Tracks available in this library"
|
|||
msgstr "المَقاطِع المتوفّرة في هذه المكتبة"
|
||||
|
||||
#: front/src/components/library/Artist.vue:54
|
||||
#, fuzzy
|
||||
msgid "Tracks by this artist"
|
||||
msgstr "مِن ألبومات هذا الفنان"
|
||||
msgstr "مَقاطِع لهذا الفنان"
|
||||
|
||||
#: front/src/components/instance/Stats.vue:25
|
||||
msgid "Tracks favorited"
|
||||
|
@ -2275,7 +2277,7 @@ msgstr ""
|
|||
|
||||
#: front/src/components/manage/library/FilesTable.vue:41
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
msgstr "النوع"
|
||||
|
||||
#: front/src/components/About.vue:15
|
||||
msgid "Unfortunately, owners of this instance did not yet take the time to complete this page."
|
||||
|
@ -2294,9 +2296,8 @@ msgid "Unmute"
|
|||
msgstr "إلغاء الكتم"
|
||||
|
||||
#: front/src/components/auth/Settings.vue:50
|
||||
#, fuzzy
|
||||
msgid "Update avatar"
|
||||
msgstr "تحديث قائمة المَقاطِع الموسيقية"
|
||||
msgstr "تحديث الصورة الرمزية"
|
||||
|
||||
#: front/src/components/playlists/Form.vue:33
|
||||
msgid "Update playlist"
|
||||
|
@ -2317,7 +2318,7 @@ msgstr "أرسل"
|
|||
|
||||
#: front/src/components/auth/Settings.vue:45
|
||||
msgid "Upload a new avatar"
|
||||
msgstr ""
|
||||
msgstr "إرسال صورة رمزية جديدة"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:7
|
||||
msgid "Uploaded files or external source"
|
||||
|
@ -2349,7 +2350,7 @@ msgstr ""
|
|||
|
||||
#: front/src/components/manage/users/InvitationsTable.vue:49
|
||||
msgid "Used"
|
||||
msgstr ""
|
||||
msgstr "مُستخدَم"
|
||||
|
||||
#: front/src/components/manage/library/RequestsTable.vue:47
|
||||
msgid "User"
|
||||
|
@ -2360,9 +2361,8 @@ msgid "User activity"
|
|||
msgstr "نشاط المستخدِم"
|
||||
|
||||
#: front/src/components/library/Radios.vue:20
|
||||
#, fuzzy
|
||||
msgid "User radios"
|
||||
msgstr "المستخدِمون"
|
||||
msgstr "إذاعات المستخدِمين"
|
||||
|
||||
#: front/src/components/auth/Signup.vue:19
|
||||
#: front/src/components/manage/users/UsersTable.vue:37
|
||||
|
@ -2394,24 +2394,23 @@ msgstr "إطّلع عليه على ميوزيك براينز"
|
|||
|
||||
#: front/src/components/playlists/PlaylistModal.vue:20
|
||||
msgid "We cannot add the track to a playlist"
|
||||
msgstr ""
|
||||
msgstr "لا يمكننا إضافة المَقطَع إلى قائمة التشغيل"
|
||||
|
||||
#: front/src/components/playlists/Form.vue:14
|
||||
msgid "We cannot create the playlist"
|
||||
msgstr ""
|
||||
msgstr "لا يمكننا إنشاء قائمة التشغيل"
|
||||
|
||||
#: front/src/components/auth/Signup.vue:13
|
||||
msgid "We cannot create your account"
|
||||
msgstr ""
|
||||
msgstr "لا يمكننا إنشاء حسابك"
|
||||
|
||||
#: front/src/components/auth/Login.vue:7
|
||||
msgid "We cannot log you in"
|
||||
msgstr ""
|
||||
msgstr "تعذر علينا تسجيل دخولك"
|
||||
|
||||
#: front/src/components/auth/Settings.vue:38
|
||||
#, fuzzy
|
||||
msgid "We cannot save your avatar"
|
||||
msgstr "تعذّر علينا حفظ إعداداتك"
|
||||
msgstr "تعذّر علينا حفظ صورتك الرمزية"
|
||||
|
||||
#: front/src/components/auth/Settings.vue:14
|
||||
msgid "We cannot save your settings"
|
||||
|
@ -2431,7 +2430,7 @@ msgstr "نعتقد أنّ الاستماع إلى الموسيقى ينبغي أ
|
|||
|
||||
#: front/src/components/PageNotFound.vue:10
|
||||
msgid "We're sorry, the page you asked for does not exist:"
|
||||
msgstr ""
|
||||
msgstr "المعذرة، إنّ الصفحة التي قمت بطلبها غير موجودة :"
|
||||
|
||||
#: front/src/components/requests/Form.vue:21
|
||||
msgid "We've received your request, you'll get some groove soon ;)"
|
||||
|
@ -2463,15 +2462,15 @@ msgstr "نعم"
|
|||
|
||||
#: front/src/components/auth/Logout.vue:8
|
||||
msgid "Yes, log me out!"
|
||||
msgstr ""
|
||||
msgstr "نعم، أؤكد الخروج !"
|
||||
|
||||
#: front/src/components/auth/Logout.vue:7
|
||||
msgid "You are currently logged in as %{ username }"
|
||||
msgstr ""
|
||||
msgstr "أنت متّصل حاليا بصفة %{ username }"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:111
|
||||
msgid "You can also skip this step and enter metadata manually."
|
||||
msgstr ""
|
||||
msgstr "يمكنك طبعًا تخطي هذه الخطوة و إدخال البيانات الوصفية يدويًا."
|
||||
|
||||
#: front/src/components/Home.vue:136
|
||||
msgid "You can invite friends and family to your instance so they can enjoy your music"
|
||||
|
@ -2487,7 +2486,7 @@ msgstr ""
|
|||
|
||||
#: front/src/components/Sidebar.vue:156
|
||||
msgid "You have a radio playing"
|
||||
msgstr ""
|
||||
msgstr "إنك تستمع إلى إذاعة"
|
||||
|
||||
#: front/src/App.vue:6
|
||||
msgid "You need to select an instance in order to continue"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,7 +8,7 @@ msgstr ""
|
|||
"Project-Id-Version: front 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-07-22 14:12+0200\n"
|
||||
"PO-Revision-Date: 2018-07-22 15:50+0000\n"
|
||||
"PO-Revision-Date: 2018-07-24 19:50+0000\n"
|
||||
"Last-Translator: Baptiste Gelez <baptiste@gelez.xyz>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: eo\n"
|
||||
|
@ -34,8 +34,8 @@ msgstr "(malplena)"
|
|||
#: front/src/components/common/ActionTable.vue:51
|
||||
msgid "%{ count } on %{ total } selected"
|
||||
msgid_plural "%{ count } on %{ total } selected"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "Unu el %{total} estas selekta"
|
||||
msgstr[1] "%{count} el %{total} estas selektaj"
|
||||
|
||||
#: front/src/components/Sidebar.vue:116
|
||||
#: src/views/federation/LibraryDetail.vue:87
|
||||
|
@ -739,11 +739,11 @@ msgstr "Tajpu nomon de artisto…"
|
|||
|
||||
#: front/src/views/federation/LibraryList.vue:122
|
||||
msgid "Enter an library domain name..."
|
||||
msgstr ""
|
||||
msgstr "Tajpu domajna nomo de instance…"
|
||||
|
||||
#: front/src/views/playlists/List.vue:104
|
||||
msgid "Enter an playlist name..."
|
||||
msgstr ""
|
||||
msgstr "Tajpu ludlistan nomon…"
|
||||
|
||||
#: front/src/components/auth/Signup.vue:98
|
||||
msgid "Enter your email"
|
||||
|
@ -884,86 +884,91 @@ msgstr "Filtri albumtipoj"
|
|||
|
||||
#: front/src/components/library/radios/Builder.vue:56
|
||||
msgid "Filter name"
|
||||
msgstr ""
|
||||
msgstr "Filtri nomon"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:52
|
||||
msgid "Finish import"
|
||||
msgstr ""
|
||||
msgstr "Fini importadon"
|
||||
|
||||
#: front/src/components/library/import/BatchDetail.vue:54
|
||||
msgid "Finished"
|
||||
msgstr ""
|
||||
msgstr "Finanto"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:59
|
||||
msgid "First, choose where you want to import the music from"
|
||||
msgstr ""
|
||||
msgstr "Unue, elekti ejo el vi volas importi muzikon"
|
||||
|
||||
#: front/src/components/federation/LibraryCard.vue:44
|
||||
msgid "Follow"
|
||||
msgstr ""
|
||||
msgstr "Sekvi"
|
||||
|
||||
#: front/src/components/federation/LibraryCard.vue:36
|
||||
msgid "Follow request pending approval"
|
||||
msgstr ""
|
||||
msgstr "Peto da sekvado atendis konsenton"
|
||||
|
||||
#: front/src/views/federation/LibraryDetail.vue:21
|
||||
msgid "Follow status"
|
||||
msgstr ""
|
||||
msgstr "Sekva statuso"
|
||||
|
||||
#: front/src/views/federation/Base.vue:13
|
||||
#: front/src/views/federation/LibraryFollowersList.vue:24
|
||||
msgid "Followers"
|
||||
msgstr ""
|
||||
msgstr "Sekvantoj"
|
||||
|
||||
#: front/src/components/federation/LibraryCard.vue:18
|
||||
msgid "Followers only"
|
||||
msgstr ""
|
||||
msgstr "Nur sekvantoj"
|
||||
|
||||
#: front/src/components/federation/LibraryCard.vue:15
|
||||
#: front/src/views/federation/LibraryDetail.vue:29
|
||||
msgid "Following"
|
||||
msgstr ""
|
||||
msgstr "Sekvata"
|
||||
|
||||
#: front/src/components/activity/Like.vue:12
|
||||
#: src/components/activity/Listen.vue:12
|
||||
msgid "from %{ album } by %{ artist }"
|
||||
msgstr ""
|
||||
msgstr "el %{album} je %{artist}"
|
||||
|
||||
#: front/src/components/library/Track.vue:13
|
||||
msgid "From album %{ album } by %{ artist }"
|
||||
msgstr ""
|
||||
msgstr "El %{album} albumo je %{artist}"
|
||||
|
||||
#: front/src/App.vue:56
|
||||
msgid "Funkwhale is a free and open-source project run by volunteers. You can help us improve the platform by reporting bugs, suggesting features and share the project with your friends!"
|
||||
msgstr ""
|
||||
"Funkwhale estas senpaga kaj kun libera fontkodo projekto, ke viglas dankon "
|
||||
"al vonlontuloj. Vi povas helpi nin plibonigi tiun kun cimosignaladoj, "
|
||||
"trajtosugestoj kaj diskonigado de la projekto al viaj amikoj!"
|
||||
|
||||
#: front/src/components/auth/SubsonicTokenForm.vue:7
|
||||
msgid "Funkwhale is compatible with other music players that support the Subsonic API."
|
||||
msgstr ""
|
||||
msgstr "Funkwhale funkcias kun aliaj muzikludiloj ke apogas la Subsonic API."
|
||||
|
||||
#: front/src/components/Home.vue:98
|
||||
msgid "Funkwhale is dead simple to use."
|
||||
msgstr ""
|
||||
msgstr "Uzi Funkwhale facilegas."
|
||||
|
||||
#: front/src/components/Home.vue:39
|
||||
msgid "Funkwhale is designed to make it easy to listen to music you like, or to discover new artists."
|
||||
msgstr ""
|
||||
"Funkwhale estas dizajna tiel ke estas facile aŭskulti muzikon vi ŝatas, aŭ "
|
||||
"malkovri novajn artistojn."
|
||||
|
||||
#: front/src/components/Home.vue:119
|
||||
msgid "Funkwhale is free and gives you control on your music."
|
||||
msgstr ""
|
||||
msgstr "Funkwhale estas senpaga kaj lasis vin estri vian muzikon."
|
||||
|
||||
#: front/src/components/Home.vue:66
|
||||
msgid "Funkwhale takes care of handling your music"
|
||||
msgstr ""
|
||||
msgstr "Funkwhale atentas manipuli vian muzikon"
|
||||
|
||||
#: front/src/components/manage/users/InvitationForm.vue:16
|
||||
msgid "Get a new invitation"
|
||||
msgstr ""
|
||||
msgstr "Akiri novan inviton"
|
||||
|
||||
#: front/src/components/Home.vue:13
|
||||
msgid "Get me to the library"
|
||||
msgstr ""
|
||||
msgstr "Iru al la muzikejo"
|
||||
|
||||
#: front/src/components/Home.vue:77
|
||||
msgid ""
|
||||
|
@ -972,19 +977,21 @@ msgid ""
|
|||
" MusicBrainz\n"
|
||||
" </a>"
|
||||
msgstr ""
|
||||
"Akiri bonegaj metadatumoj pri vian muzikon kun <a href=\"https://musicbrainz."
|
||||
"org\" target=\"_blank\">MusicBrainz</a>"
|
||||
|
||||
#: front/src/components/common/ActionTable.vue:21
|
||||
#: front/src/components/common/ActionTable.vue:27
|
||||
msgid "Go"
|
||||
msgstr ""
|
||||
msgstr "Komenci"
|
||||
|
||||
#: front/src/components/PageNotFound.vue:14
|
||||
msgid "Go to home page"
|
||||
msgstr ""
|
||||
msgstr "Iru hejme"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:13
|
||||
msgid "Grab corresponding metadata"
|
||||
msgstr ""
|
||||
msgstr "Kolekti la koncernajn metadatumojn"
|
||||
|
||||
#: front/src/App.vue:74
|
||||
msgid "Help us translate Funkwhale"
|
||||
|
@ -992,237 +999,241 @@ msgstr ""
|
|||
|
||||
#: front/src/components/library/Home.vue:65
|
||||
msgid "Home"
|
||||
msgstr ""
|
||||
msgstr "Hejmo"
|
||||
|
||||
#: front/src/components/instance/Stats.vue:36
|
||||
msgid "Hours of music"
|
||||
msgstr ""
|
||||
msgstr "Muzikhoroj"
|
||||
|
||||
#: front/src/components/auth/SubsonicTokenForm.vue:11
|
||||
msgid "However, accessing Funkwhale from those clients require a separate password you can set below."
|
||||
msgstr ""
|
||||
"Tamen, atingi Funkwhale el tiuj aplikaĵo bezonas alian pasvorton ke vi povas "
|
||||
"difini malsupre."
|
||||
|
||||
#: front/src/components/library/import/BatchList.vue:34
|
||||
msgid "ID"
|
||||
msgstr ""
|
||||
msgstr "ID"
|
||||
|
||||
#: front/src/views/auth/PasswordResetConfirm.vue:24
|
||||
msgid "If the email address provided in the previous step is valid and binded to a user account, you should receive an email with reset instructions in the next couple of minutes."
|
||||
msgstr ""
|
||||
"Se la retadreso provizanta dum la antaŭa etapo korektas kaj bindas al "
|
||||
"uzantkonto, vi baldaŭ ricevus retmesaĝon kun renuligadaj instrukcioj."
|
||||
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:196
|
||||
#: front/src/components/library/Library.vue:17
|
||||
msgid "Import"
|
||||
msgstr ""
|
||||
msgstr "Importi"
|
||||
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:57
|
||||
msgid "Import #%{ id } launched"
|
||||
msgstr ""
|
||||
msgstr "Importado #%{id} komencis"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:38
|
||||
msgid "Import %{ count } track"
|
||||
msgid_plural "Import %{ count } tracks"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "Importi unu kanto"
|
||||
msgstr[1] "Importi %{count} kantoj"
|
||||
|
||||
#: front/src/components/library/import/BatchDetail.vue:10
|
||||
msgid "Import batch"
|
||||
msgstr ""
|
||||
msgstr "Importaro"
|
||||
|
||||
#: front/src/components/library/import/BatchDetail.vue:185
|
||||
msgid "Import Batch #%{ id }"
|
||||
msgstr ""
|
||||
msgstr "Importaro #%{id}"
|
||||
|
||||
#: front/src/components/library/Library.vue:20
|
||||
msgid "Import batches"
|
||||
msgstr ""
|
||||
msgstr "Importaroj"
|
||||
|
||||
#: front/src/components/library/import/BatchList.vue:117
|
||||
msgid "Import Batches"
|
||||
msgstr ""
|
||||
msgstr "Importaroj"
|
||||
|
||||
#: front/src/components/manage/library/FilesTable.vue:40
|
||||
#: front/src/components/manage/library/RequestsTable.vue:53
|
||||
msgid "Import date"
|
||||
msgstr ""
|
||||
msgstr "Importdato"
|
||||
|
||||
#: front/src/components/library/import/FileUpload.vue:38
|
||||
msgid "Import detail page"
|
||||
msgstr ""
|
||||
msgstr "Importado detalpaĝo"
|
||||
|
||||
#: front/src/components/Sidebar.vue:81
|
||||
msgid "Import music"
|
||||
msgstr ""
|
||||
msgstr "Importi muzikon"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:267
|
||||
msgid "Import Music"
|
||||
msgstr ""
|
||||
msgstr "Importi muzikon"
|
||||
|
||||
#: front/src/components/Home.vue:71
|
||||
msgid "Import music from various platforms, such as YouTube or SoundCloud"
|
||||
msgstr ""
|
||||
msgstr "Importi muzikon el multe da servicoj, kiel YouTube aŭ SoundCloud"
|
||||
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:14
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:66
|
||||
msgid "Import pending"
|
||||
msgstr ""
|
||||
msgstr "Importado atendas"
|
||||
|
||||
#: front/src/views/admin/library/Base.vue:9
|
||||
#: front/src/views/admin/library/RequestsList.vue:3
|
||||
#: front/src/views/admin/library/RequestsList.vue:21
|
||||
msgid "Import requests"
|
||||
msgstr ""
|
||||
msgstr "Importpetoj"
|
||||
|
||||
#: front/src/components/library/import/BatchList.vue:20
|
||||
#: front/src/components/library/import/Main.vue:6
|
||||
msgid "Import source"
|
||||
msgstr ""
|
||||
msgstr "Importfonto"
|
||||
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:9
|
||||
msgid "Import status"
|
||||
msgstr ""
|
||||
msgstr "Importstato"
|
||||
|
||||
#: front/src/components/library/import/ReleaseImport.vue:14
|
||||
msgid "Import this release"
|
||||
msgstr ""
|
||||
msgstr "Importi tiun albumon"
|
||||
|
||||
#: front/src/components/library/import/TrackImport.vue:11
|
||||
msgid "Import this track"
|
||||
msgstr ""
|
||||
msgstr "Importi tiun kanton"
|
||||
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:12
|
||||
#: front/src/components/manage/library/RequestsTable.vue:29
|
||||
#: front/src/components/manage/library/RequestsTable.vue:61
|
||||
msgid "Imported"
|
||||
msgstr ""
|
||||
msgstr "Importata"
|
||||
|
||||
#: front/src/components/library/import/TrackImport.vue:44
|
||||
msgid "Imported URL"
|
||||
msgstr ""
|
||||
msgstr "Importanta URL"
|
||||
|
||||
#: front/src/views/admin/Settings.vue:82
|
||||
msgid "Imports"
|
||||
msgstr ""
|
||||
msgstr "Importadoj"
|
||||
|
||||
#: front/src/components/favorites/TrackFavoriteIcon.vue:3
|
||||
msgid "In favorites"
|
||||
msgstr ""
|
||||
msgstr "En stelumoj"
|
||||
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:65
|
||||
msgid "In library"
|
||||
msgstr ""
|
||||
msgstr "En muzikejo"
|
||||
|
||||
#: front/src/components/manage/users/UsersTable.vue:54
|
||||
msgid "Inactive"
|
||||
msgstr ""
|
||||
msgstr "Malaktiva"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:96
|
||||
msgid "Input a MusicBrainz ID manually:"
|
||||
msgstr ""
|
||||
msgstr "Tajpu MusicBrainz ID mane:"
|
||||
|
||||
#: front/src/views/auth/PasswordReset.vue:53
|
||||
msgid "Input the email address binded to your account"
|
||||
msgstr ""
|
||||
msgstr "Tajpu la retadreson bindanta al via konto"
|
||||
|
||||
#: front/src/components/playlists/Editor.vue:31
|
||||
msgid "Insert from queue (%{ count } track)"
|
||||
msgid_plural "Insert from queue (%{ count } tracks)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "Internigi el atendovico (unu kanto)"
|
||||
msgstr[1] "Internigi el atendovico (%{count} kantoj)"
|
||||
|
||||
#: front/src/views/admin/Settings.vue:80
|
||||
msgid "Instance information"
|
||||
msgstr ""
|
||||
msgstr "Instanca informo"
|
||||
|
||||
#: front/src/components/library/Radios.vue:9
|
||||
msgid "Instance radios"
|
||||
msgstr ""
|
||||
msgstr "Instancaj radioj"
|
||||
|
||||
#: front/src/views/admin/Settings.vue:75
|
||||
msgid "Instance settings"
|
||||
msgstr ""
|
||||
msgstr "Instancaj preferoj"
|
||||
|
||||
#: front/src/views/instance/Timeline.vue:57
|
||||
msgid "Instance Timeline"
|
||||
msgstr ""
|
||||
msgstr "Instanca tempolino"
|
||||
|
||||
#: front/src/components/auth/Signup.vue:42
|
||||
#: front/src/components/manage/users/InvitationForm.vue:11
|
||||
msgid "Invitation code"
|
||||
msgstr ""
|
||||
msgstr "Invita kodo"
|
||||
|
||||
#: front/src/components/auth/Signup.vue:43
|
||||
msgid "Invitation code (optional)"
|
||||
msgstr ""
|
||||
msgstr "Invita kodo (nedeviga)"
|
||||
|
||||
#: front/src/views/admin/users/Base.vue:8
|
||||
#: src/views/admin/users/InvitationsList.vue:3
|
||||
#: front/src/views/admin/users/InvitationsList.vue:24
|
||||
msgid "Invitations"
|
||||
msgstr ""
|
||||
msgstr "Invitoj"
|
||||
|
||||
#: front/src/App.vue:43
|
||||
msgid "Issue tracker"
|
||||
msgstr ""
|
||||
msgstr "Cimspuradilo"
|
||||
|
||||
#: front/src/components/library/import/BatchDetail.vue:80
|
||||
msgid "Job ID"
|
||||
msgstr ""
|
||||
msgstr "Labora ID"
|
||||
|
||||
#: front/src/components/library/import/BatchList.vue:36
|
||||
msgid "Jobs"
|
||||
msgstr ""
|
||||
msgstr "Laboroj"
|
||||
|
||||
#: front/src/components/Home.vue:50
|
||||
msgid "Keep a track of your favorite songs"
|
||||
msgstr ""
|
||||
msgstr "Konservi postsignojn de viaj preferitaj kantoj"
|
||||
|
||||
#: front/src/components/audio/track/Table.vue:33
|
||||
msgid "Keep your PRIVATE_TOKEN secret as it gives access to your account."
|
||||
msgstr ""
|
||||
msgstr "Konservu vian PRIVATE_TOKEN sekreta, ĉar ĝi atingeblas vian konton."
|
||||
|
||||
#: front/src/components/manage/users/UsersTable.vue:41
|
||||
#: front/src/views/admin/users/UsersDetail.vue:45
|
||||
msgid "Last activity"
|
||||
msgstr ""
|
||||
msgstr "Lasta akto"
|
||||
|
||||
#: front/src/views/federation/LibraryDetail.vue:101
|
||||
msgid "Last fetched"
|
||||
msgstr ""
|
||||
msgstr "Lasta kolektado"
|
||||
|
||||
#: front/src/components/playlists/PlaylistModal.vue:32
|
||||
msgid "Last modification"
|
||||
msgstr ""
|
||||
msgstr "Lasta redakto"
|
||||
|
||||
#: front/src/components/common/ActionTable.vue:39
|
||||
msgid "Launch"
|
||||
msgstr ""
|
||||
msgstr "Lanĉi"
|
||||
|
||||
#: front/src/components/library/import/BatchDetail.vue:18
|
||||
#: front/src/components/library/import/BatchList.vue:35
|
||||
msgid "Launch date"
|
||||
msgstr ""
|
||||
msgstr "Lanĉa dato"
|
||||
|
||||
#: front/src/components/federation/LibraryForm.vue:31
|
||||
msgid "Launch scan"
|
||||
msgstr ""
|
||||
msgstr "Lanĉi skanon"
|
||||
|
||||
#: front/src/components/Home.vue:10
|
||||
msgid "Learn more about this instance"
|
||||
msgstr ""
|
||||
msgstr "Lerni pli pri tiu instanco"
|
||||
|
||||
#: front/src/components/manage/users/InvitationForm.vue:58
|
||||
msgid "Leave empty for a random code"
|
||||
msgstr ""
|
||||
msgstr "Lasu malplena por hazarda kodo"
|
||||
|
||||
#: front/src/components/requests/Form.vue:10
|
||||
msgid "Leave this field empty if you're requesting the whole discography."
|
||||
msgstr ""
|
||||
msgstr "Lasu malplena se vi volas la tutan albumaron."
|
||||
|
||||
#: front/src/views/federation/Base.vue:5
|
||||
#: src/views/federation/LibraryList.vue:123
|
||||
msgid "Libraries"
|
||||
msgstr ""
|
||||
msgstr "Muzikejoj"
|
||||
|
||||
#: front/src/components/Sidebar.vue:70
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:51
|
||||
|
@ -1231,103 +1242,104 @@ msgstr ""
|
|||
#: front/src/views/admin/users/UsersDetail.vue:157
|
||||
#: front/src/views/federation/LibraryDetail.vue:194
|
||||
msgid "Library"
|
||||
msgstr ""
|
||||
msgstr "Muzikejo"
|
||||
|
||||
#: front/src/views/admin/library/FilesList.vue:3
|
||||
msgid "Library files"
|
||||
msgstr ""
|
||||
msgstr "Muzikejaj dosieroj"
|
||||
|
||||
#: front/src/components/federation/LibraryForm.vue:20
|
||||
msgid "Library name"
|
||||
msgstr ""
|
||||
msgstr "Nomo de muzikejo"
|
||||
|
||||
#: front/src/views/federation/LibraryDetail.vue:84
|
||||
msgid "Library size"
|
||||
msgstr ""
|
||||
msgstr "Muzikejgrando"
|
||||
|
||||
#: front/src/components/federation/LibraryForm.vue:96
|
||||
msgid "library@demo.funkwhale.audio"
|
||||
msgstr ""
|
||||
msgstr "library@demo.funkwhale.audio"
|
||||
|
||||
#: front/src/App.vue:29
|
||||
msgid "Links"
|
||||
msgstr ""
|
||||
msgstr "Ligiloj"
|
||||
|
||||
#: front/src/views/instance/Timeline.vue:4
|
||||
msgid "Loading timeline..."
|
||||
msgstr ""
|
||||
msgstr "Tempolino ŝarĝas…"
|
||||
|
||||
#: front/src/components/favorites/List.vue:5
|
||||
#, fuzzy
|
||||
msgid "Loading your favorites..."
|
||||
msgstr ""
|
||||
msgstr "Ŝarĝantas stelumojn"
|
||||
|
||||
#: front/src/components/auth/Login.vue:78
|
||||
msgid "Log In"
|
||||
msgstr ""
|
||||
msgstr "Ensaluti"
|
||||
|
||||
#: front/src/components/auth/Login.vue:4
|
||||
msgid "Log in to your Funkwhale account"
|
||||
msgstr ""
|
||||
msgstr "Ensaluti en via Funkwhale konto"
|
||||
|
||||
#: front/src/components/auth/Logout.vue:20
|
||||
msgid "Log Out"
|
||||
msgstr ""
|
||||
msgstr "Elsaluti"
|
||||
|
||||
#: front/src/components/Sidebar.vue:38
|
||||
msgid "Logged in as %{ username }"
|
||||
msgstr ""
|
||||
msgstr "Elsuta je %{username}"
|
||||
|
||||
#: front/src/components/Sidebar.vue:44 src/components/auth/Login.vue:41
|
||||
msgid "Login"
|
||||
msgstr ""
|
||||
msgstr "Ensaluti"
|
||||
|
||||
#: front/src/components/Sidebar.vue:43
|
||||
msgid "Logout"
|
||||
msgstr ""
|
||||
msgstr "Elsaluti"
|
||||
|
||||
#: front/src/components/audio/Player.vue:266
|
||||
msgid "Looping disabled. Click to switch to single-track looping."
|
||||
msgstr ""
|
||||
msgstr "Ripeto malaktivas. Alklaki por aktivi ripetado de la aktuala kanto."
|
||||
|
||||
#: front/src/components/audio/Player.vue:267
|
||||
msgid "Looping on a single track. Click to switch to whole queue looping."
|
||||
msgstr ""
|
||||
msgstr "Ripetas unu kanton. Alklaki por aktivi ripetado de la tutan atendovico."
|
||||
|
||||
#: front/src/components/audio/Player.vue:268
|
||||
msgid "Looping on whole queue. Click to disable looping."
|
||||
msgstr ""
|
||||
msgstr "Ripetas la tutan atendovicon. Alklaki por malaktivi ripeto."
|
||||
|
||||
#: front/src/components/library/Track.vue:94
|
||||
msgid "Lyrics"
|
||||
msgstr ""
|
||||
msgstr "Teksto"
|
||||
|
||||
#: front/src/views/admin/library/Base.vue:25
|
||||
msgid "Manage library"
|
||||
msgstr ""
|
||||
msgstr "Manipuli muzikejon"
|
||||
|
||||
#: front/src/components/playlists/PlaylistModal.vue:3
|
||||
msgid "Manage playlists"
|
||||
msgstr ""
|
||||
msgstr "Manipuli ludlistojn"
|
||||
|
||||
#: front/src/views/admin/users/Base.vue:20
|
||||
msgid "Manage users"
|
||||
msgstr ""
|
||||
msgstr "Manipuli uzantojn"
|
||||
|
||||
#: front/src/views/playlists/List.vue:8
|
||||
msgid "Manage your playlists"
|
||||
msgstr ""
|
||||
msgstr "Manipuli viajn ludlistojn"
|
||||
|
||||
#: front/src/components/manage/library/RequestsTable.vue:197
|
||||
msgid "Mark as closed"
|
||||
msgstr ""
|
||||
msgstr "Marki fermata"
|
||||
|
||||
#: front/src/components/manage/library/RequestsTable.vue:196
|
||||
msgid "Mark as imported"
|
||||
msgstr ""
|
||||
msgstr "Marki importata"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:12
|
||||
msgid "Metadata"
|
||||
msgstr ""
|
||||
msgstr "Metadatumoj"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:115
|
||||
msgid ""
|
||||
|
@ -1337,31 +1349,36 @@ msgid ""
|
|||
" </a>\n"
|
||||
" project, which you can think about as the Wikipedia of music."
|
||||
msgstr ""
|
||||
"Metadatumoj estas datumoj rilatanta al muziko vi volas importi. Ĝi enhavas "
|
||||
"ĉiu informo pri artistoj, albumoj kaj kantoj. Por havi bonega muzikejo, "
|
||||
"estas rekomendata kolekti datumojn el la <a href=\"https://musicbrainz.org\" "
|
||||
"target=\"_blank\">MusicBrainz</a> projekto, ke similas al Vikipedio sed por "
|
||||
"muziko."
|
||||
|
||||
#: front/src/components/Sidebar.vue:48
|
||||
#: src/components/library/import/Main.vue:18
|
||||
msgid "Music"
|
||||
msgstr ""
|
||||
msgstr "Muziko"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:147
|
||||
msgid "Music request"
|
||||
msgstr ""
|
||||
msgstr "Muzikpeto"
|
||||
|
||||
#: front/src/components/audio/Player.vue:265
|
||||
msgid "Mute"
|
||||
msgstr ""
|
||||
msgstr "Silentigi"
|
||||
|
||||
#: front/src/components/Sidebar.vue:34
|
||||
msgid "My account"
|
||||
msgstr ""
|
||||
msgstr "Mia konto"
|
||||
|
||||
#: front/src/components/playlists/Form.vue:74
|
||||
msgid "My awesome playlist"
|
||||
msgstr ""
|
||||
msgstr "Mia mojosa ludlisto"
|
||||
|
||||
#: front/src/components/library/radios/Builder.vue:227
|
||||
msgid "My awesome radio"
|
||||
msgstr ""
|
||||
msgstr "Mia mojosa radio"
|
||||
|
||||
#: front/src/components/library/Track.vue:64
|
||||
#: src/components/library/Track.vue:75
|
||||
|
@ -1376,83 +1393,85 @@ msgstr ""
|
|||
#: front/src/components/manage/users/UsersTable.vue:61
|
||||
#: front/src/views/admin/users/UsersDetail.vue:49
|
||||
msgid "N/A"
|
||||
msgstr ""
|
||||
msgstr "ND"
|
||||
|
||||
#: front/src/components/playlists/PlaylistModal.vue:31
|
||||
#: front/src/views/admin/users/UsersDetail.vue:21
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
msgstr "Nomo"
|
||||
|
||||
#: front/src/components/auth/Settings.vue:88
|
||||
#: front/src/views/auth/PasswordResetConfirm.vue:14
|
||||
msgid "New password"
|
||||
msgstr ""
|
||||
msgstr "Nova pasvorto"
|
||||
|
||||
#: front/src/components/Sidebar.vue:158
|
||||
msgid "New tracks will be appended here automatically."
|
||||
msgstr ""
|
||||
msgstr "Novaj kantoj estos aldonataj ĉi-tie aŭtomate."
|
||||
|
||||
#: front/src/components/library/import/Main.vue:29
|
||||
msgid "Next step"
|
||||
msgstr ""
|
||||
msgstr "Baldaŭa etapo"
|
||||
|
||||
#: front/src/components/audio/Player.vue:263
|
||||
msgid "Next track"
|
||||
msgstr ""
|
||||
msgstr "Baldaŭa kanto"
|
||||
|
||||
#: front/src/components/Sidebar.vue:125
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
msgstr "Ne"
|
||||
|
||||
#: front/src/components/Home.vue:103
|
||||
msgid "No add-ons, no plugins : you only need a web library"
|
||||
msgstr ""
|
||||
msgstr "Nek aldonaĵoj, nek kromprogramoj: vi nur bezonas retmuzikejo"
|
||||
|
||||
#: front/src/components/library/Track.vue:102
|
||||
msgid "No lyrics available for this track."
|
||||
msgstr ""
|
||||
msgstr "Nenio teksto disponeblas por tiu kanto."
|
||||
|
||||
#: front/src/components/playlists/Form.vue:81
|
||||
msgid "Nobody except me"
|
||||
msgstr ""
|
||||
msgstr "Neniu krom mi"
|
||||
|
||||
#: front/src/views/federation/LibraryDetail.vue:32
|
||||
msgid "Not following"
|
||||
msgstr ""
|
||||
msgstr "Ne sekvas"
|
||||
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:13
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:67
|
||||
msgid "Not imported"
|
||||
msgstr ""
|
||||
msgstr "Ne importintas"
|
||||
|
||||
#: front/src/components/manage/users/InvitationsTable.vue:51
|
||||
msgid "Not used"
|
||||
msgstr ""
|
||||
msgstr "Ne uzantata"
|
||||
|
||||
#: front/src/App.vue:37
|
||||
msgid "Official website"
|
||||
msgstr ""
|
||||
msgstr "Oficiala retejo"
|
||||
|
||||
#: front/src/components/auth/Settings.vue:83
|
||||
msgid "Old password"
|
||||
msgstr ""
|
||||
msgstr "Malnova pasvorto"
|
||||
|
||||
#: front/src/components/library/import/FileUpload.vue:36
|
||||
msgid "Once all your files are uploaded, simply click the following button to check the import status."
|
||||
msgstr ""
|
||||
"Kiam viaj dosieroj estas tute elŝutinta, ĵus alklaki tiu butono por vidi la "
|
||||
"staton de la importo."
|
||||
|
||||
#: front/src/components/federation/LibraryCard.vue:21
|
||||
#: front/src/components/manage/users/InvitationsTable.vue:20
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
msgstr "Malferma"
|
||||
|
||||
#: front/src/App.vue:63
|
||||
msgid "Options"
|
||||
msgstr ""
|
||||
msgstr "Preferoj"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:93
|
||||
msgid "Or"
|
||||
msgstr ""
|
||||
msgstr "Aŭ"
|
||||
|
||||
#: front/src/components/favorites/List.vue:23
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:18
|
||||
|
@ -1465,7 +1484,7 @@ msgstr ""
|
|||
#: front/src/views/federation/LibraryList.vue:18
|
||||
#: src/views/playlists/List.vue:17
|
||||
msgid "Ordering"
|
||||
msgstr ""
|
||||
msgstr "Ordo"
|
||||
|
||||
#: front/src/components/favorites/List.vue:31
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:26
|
||||
|
@ -1477,35 +1496,35 @@ msgstr ""
|
|||
#: front/src/views/federation/LibraryList.vue:26
|
||||
#: src/views/playlists/List.vue:25
|
||||
msgid "Ordering direction"
|
||||
msgstr ""
|
||||
msgstr "Orda direkto"
|
||||
|
||||
#: front/src/components/manage/users/InvitationsTable.vue:38
|
||||
msgid "Owner"
|
||||
msgstr ""
|
||||
msgstr "Proprietulo"
|
||||
|
||||
#: front/src/components/PageNotFound.vue:33
|
||||
msgid "Page Not Found"
|
||||
msgstr ""
|
||||
msgstr "Ne eblas trovi tiun paĝon"
|
||||
|
||||
#: front/src/components/PageNotFound.vue:7
|
||||
msgid "Page not found!"
|
||||
msgstr ""
|
||||
msgstr "Maltrovitas paĝon!"
|
||||
|
||||
#: front/src/components/auth/Login.vue:32 src/components/auth/Signup.vue:38
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
msgstr "Pasvorto"
|
||||
|
||||
#: front/src/components/auth/SubsonicTokenForm.vue:95
|
||||
msgid "Password updated"
|
||||
msgstr ""
|
||||
msgstr "Pasvorto aktuliginta"
|
||||
|
||||
#: front/src/views/auth/PasswordResetConfirm.vue:28
|
||||
msgid "Password updated successfully"
|
||||
msgstr ""
|
||||
msgstr "Pasvorto sukcese aktualiginta"
|
||||
|
||||
#: front/src/components/audio/Player.vue:262
|
||||
msgid "Pause track"
|
||||
msgstr ""
|
||||
msgstr "Paŭzi kanton"
|
||||
|
||||
#: front/src/components/federation/LibraryFollowTable.vue:46
|
||||
#: front/src/components/library/import/BatchDetail.vue:33
|
||||
|
@ -1515,86 +1534,86 @@ msgstr ""
|
|||
#: front/src/components/manage/library/RequestsTable.vue:27
|
||||
#: front/src/components/manage/library/RequestsTable.vue:63
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
msgstr "Atendas"
|
||||
|
||||
#: front/src/components/federation/LibraryFollowTable.vue:11
|
||||
#: front/src/views/federation/LibraryDetail.vue:26
|
||||
msgid "Pending approval"
|
||||
msgstr ""
|
||||
msgstr "Atendas aprobon"
|
||||
|
||||
#: front/src/components/Sidebar.vue:217
|
||||
msgid "Pending follow requests"
|
||||
msgstr ""
|
||||
msgstr "Atendantaj petoj da sekvado"
|
||||
|
||||
#: front/src/components/Sidebar.vue:216 src/views/admin/library/Base.vue:26
|
||||
msgid "Pending import requests"
|
||||
msgstr ""
|
||||
msgstr "Atendantaj importpetoj"
|
||||
|
||||
#: front/src/components/requests/Form.vue:26 src/views/federation/Base.vue:36
|
||||
msgid "Pending requests"
|
||||
msgstr ""
|
||||
msgstr "Atendantaj petoj"
|
||||
|
||||
#: front/src/components/manage/users/UsersTable.vue:42
|
||||
#: front/src/views/admin/users/UsersDetail.vue:68
|
||||
msgid "Permissions"
|
||||
msgstr ""
|
||||
msgstr "Rajtoj"
|
||||
|
||||
#: front/src/components/audio/PlayButton.vue:9
|
||||
#: src/components/library/Track.vue:30
|
||||
msgid "Play"
|
||||
msgstr ""
|
||||
msgstr "Ludi"
|
||||
|
||||
#: front/src/components/audio/album/Card.vue:50
|
||||
#: front/src/components/audio/artist/Card.vue:44
|
||||
#: src/components/library/Album.vue:28 front/src/views/playlists/Detail.vue:23
|
||||
msgid "Play all"
|
||||
msgstr ""
|
||||
msgstr "Ludi ĉiu"
|
||||
|
||||
#: front/src/components/library/Artist.vue:26
|
||||
msgid "Play all albums"
|
||||
msgstr ""
|
||||
msgstr "Ludi ĉiuj albumoj"
|
||||
|
||||
#: front/src/components/audio/PlayButton.vue:58
|
||||
msgid "Play immediatly"
|
||||
msgstr ""
|
||||
msgstr "Ludi tuj"
|
||||
|
||||
#: front/src/components/audio/PlayButton.vue:15
|
||||
msgid "Play next"
|
||||
msgstr ""
|
||||
msgstr "Ludi baldaŭe"
|
||||
|
||||
#: front/src/components/audio/PlayButton.vue:16
|
||||
msgid "Play now"
|
||||
msgstr ""
|
||||
msgstr "Ludi tuj"
|
||||
|
||||
#: front/src/components/audio/Player.vue:261
|
||||
msgid "Play track"
|
||||
msgstr ""
|
||||
msgstr "Ludi kanton"
|
||||
|
||||
#: front/src/views/playlists/Detail.vue:90
|
||||
msgid "Playlist"
|
||||
msgstr ""
|
||||
msgstr "Ludlisto"
|
||||
|
||||
#: front/src/views/playlists/Detail.vue:12
|
||||
msgid "Playlist containing %{ count } track, by %{ username }"
|
||||
msgid_plural "Playlist containing %{ count } tracks, by %{ username }"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "Ludisto enhavanta unu kanto, je %{username}"
|
||||
msgstr[1] "Ludisto enhavanta %{count} kantoj, je %{username}"
|
||||
|
||||
#: front/src/components/playlists/Form.vue:9
|
||||
msgid "Playlist created"
|
||||
msgstr ""
|
||||
msgstr "Ludlisto kreiintas"
|
||||
|
||||
#: front/src/components/playlists/Editor.vue:4
|
||||
msgid "Playlist editor"
|
||||
msgstr ""
|
||||
msgstr "Ludlista redaktilo"
|
||||
|
||||
#: front/src/components/playlists/Form.vue:21
|
||||
msgid "Playlist name"
|
||||
msgstr ""
|
||||
msgstr "Nomo de la ludlisto"
|
||||
|
||||
#: front/src/components/playlists/Form.vue:6
|
||||
msgid "Playlist updated"
|
||||
msgstr ""
|
||||
msgstr "Ludlisto aktualigintas"
|
||||
|
||||
#: front/src/components/playlists/Form.vue:25
|
||||
msgid "Playlist visibility"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -3,9 +3,10 @@ msgstr ""
|
|||
"Project-Id-Version: French (Funkwhale)\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-07-22 14:12+0200\n"
|
||||
"PO-Revision-Date: 2018-07-21 21:30+0000\n"
|
||||
"Last-Translator: Baptiste Gelez <baptiste@gelez.xyz>\n"
|
||||
"Language-Team: French <https://translate.funkwhale.audio/projects/funkwhale/front/fr/>\n"
|
||||
"PO-Revision-Date: 2018-07-30 16:28+0000\n"
|
||||
"Last-Translator: Eliot Berriot <contact@eliotberriot.com>\n"
|
||||
"Language-Team: French <https://translate.funkwhale.audio/projects/funkwhale/"
|
||||
"front/fr/>\n"
|
||||
"Language: fr_FR\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
|
@ -98,7 +99,7 @@ msgstr[1] "%{ count } pistes"
|
|||
|
||||
#: front/src/components/About.vue:5
|
||||
msgid "About %{ instance }"
|
||||
msgstr "À propos de cette instance"
|
||||
msgstr "À propos de %{instance}"
|
||||
|
||||
#: front/src/App.vue:54
|
||||
msgid "About Funkwhale"
|
||||
|
@ -155,9 +156,8 @@ msgid "Actions"
|
|||
msgstr "Actions"
|
||||
|
||||
#: front/src/components/manage/users/UsersTable.vue:53
|
||||
#, fuzzy
|
||||
msgid "Active"
|
||||
msgstr "Activité"
|
||||
msgstr "Actif"
|
||||
|
||||
#: front/src/components/Sidebar.vue:60
|
||||
msgid "Activity"
|
||||
|
@ -260,7 +260,9 @@ msgstr "Une erreur s'est produite pendant l'enregistrement de vos modifications"
|
|||
|
||||
#: front/src/components/auth/Login.vue:10
|
||||
msgid "An unknown error happend, this can mean the server is down or cannot be reached"
|
||||
msgstr "Une erreur inconnue a été rencontrée, ce qui peut signifier que le serveur est en panne ou n’a pas pu être atteint"
|
||||
msgstr ""
|
||||
"Une erreur inconnue s'est produite, cela pourrait signifier que le serveur "
|
||||
"ne peut pas être accedé"
|
||||
|
||||
#: front/src/components/federation/LibraryTrackTable.vue:11
|
||||
#: front/src/components/library/import/BatchDetail.vue:68
|
||||
|
@ -985,7 +987,7 @@ msgstr "Récupérer les métadonnées correspondantes"
|
|||
|
||||
#: front/src/App.vue:74
|
||||
msgid "Help us translate Funkwhale"
|
||||
msgstr ""
|
||||
msgstr "Aidez nous à traduire Funkwhale"
|
||||
|
||||
#: front/src/components/library/Home.vue:65
|
||||
msgid "Home"
|
||||
|
@ -1111,7 +1113,7 @@ msgstr "Dans la bibliothèque"
|
|||
|
||||
#: front/src/components/manage/users/UsersTable.vue:54
|
||||
msgid "Inactive"
|
||||
msgstr "Actif"
|
||||
msgstr "Inactif"
|
||||
|
||||
#: front/src/components/library/import/Main.vue:96
|
||||
msgid "Input a MusicBrainz ID manually:"
|
||||
|
@ -1436,7 +1438,7 @@ msgstr "Site officiel"
|
|||
|
||||
#: front/src/components/auth/Settings.vue:83
|
||||
msgid "Old password"
|
||||
msgstr "Ancien mot de passe"
|
||||
msgstr "Vieux mot de passe"
|
||||
|
||||
#: front/src/components/library/import/FileUpload.vue:36
|
||||
msgid "Once all your files are uploaded, simply click the following button to check the import status."
|
||||
|
@ -1621,7 +1623,7 @@ msgstr "Merci de vérifier que votre nom d'utilisateur et mot de passe sont corr
|
|||
|
||||
#: front/src/components/auth/Settings.vue:46
|
||||
msgid "PNG, GIF or JPG. At most 2MB. Will be downscaled to 400x400px."
|
||||
msgstr "PNG, GIF ou JPG. 2Mb au plus. L'image sera réduite à 400×400 pixels."
|
||||
msgstr "PNG, GIF ou JPG. 2Mo au plus. L'image sera réduite à 400×400 pixels."
|
||||
|
||||
#: front/src/components/library/import/Main.vue:26
|
||||
msgid "Previous step"
|
||||
|
@ -1661,18 +1663,16 @@ msgid "Radio Builder"
|
|||
msgstr "Éditeur de radio"
|
||||
|
||||
#: front/src/components/library/radios/Builder.vue:15
|
||||
#, fuzzy
|
||||
msgid "Radio created"
|
||||
msgstr "Nom de la radio"
|
||||
msgstr "Radio créée"
|
||||
|
||||
#: front/src/components/library/radios/Builder.vue:21
|
||||
msgid "Radio name"
|
||||
msgstr "Nom de la radio"
|
||||
|
||||
#: front/src/components/library/radios/Builder.vue:12
|
||||
#, fuzzy
|
||||
msgid "Radio updated"
|
||||
msgstr "Nom de la radio"
|
||||
msgstr "Radio à jour"
|
||||
|
||||
#: front/src/components/library/Library.vue:10
|
||||
#: src/components/library/Radios.vue:141
|
||||
|
@ -1689,7 +1689,7 @@ msgstr "Ajoutés récemment"
|
|||
|
||||
#: front/src/components/library/Home.vue:11
|
||||
msgid "Recently favorited"
|
||||
msgstr "Ajoutées à vos favoris récemment"
|
||||
msgstr "Récemment ajouté aux favoris"
|
||||
|
||||
#: front/src/components/library/Home.vue:6
|
||||
msgid "Recently listened"
|
||||
|
@ -2297,7 +2297,7 @@ msgstr "Nom d'utilisateur ou email"
|
|||
|
||||
#: front/src/components/instance/Stats.vue:13
|
||||
msgid "users"
|
||||
msgstr "tilisateur·ice·s"
|
||||
msgstr "utilisateur·rice·s"
|
||||
|
||||
#: front/src/components/Sidebar.vue:103 src/views/admin/Settings.vue:81
|
||||
#: front/src/views/admin/users/Base.vue:5 src/views/admin/users/UsersList.vue:3
|
||||
|
@ -2352,7 +2352,7 @@ msgstr "Nous pensons que l'accès à la musique devrait être simple."
|
|||
|
||||
#: front/src/components/PageNotFound.vue:10
|
||||
msgid "We're sorry, the page you asked for does not exist:"
|
||||
msgstr "Désolé, la page demandée n’existe pas :"
|
||||
msgstr "Désolé, la page demandée n’existe pas :"
|
||||
|
||||
#: front/src/components/requests/Form.vue:21
|
||||
msgid "We've received your request, you'll get some groove soon ;)"
|
||||
|
@ -2368,7 +2368,7 @@ msgstr "Bienvenue sur Funkwhale"
|
|||
|
||||
#: front/src/components/library/import/Main.vue:114
|
||||
msgid "What is metadata?"
|
||||
msgstr "Qu'est-ce que les métadonnées ?"
|
||||
msgstr "Qu'est-ce que les métadonnées ?"
|
||||
|
||||
#: front/src/views/federation/LibraryDetail.vue:197
|
||||
msgid "When enabled, auto importing will automatically import new tracks published in this library"
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,7 +8,7 @@ msgstr ""
|
|||
"Project-Id-Version: front 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-07-22 14:12+0200\n"
|
||||
"PO-Revision-Date: 2018-07-22 20:29+0000\n"
|
||||
"PO-Revision-Date: 2018-07-24 17:46+0000\n"
|
||||
"Last-Translator: Quentí <quentin_antonin@hotmail.com>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: oc\n"
|
||||
|
@ -28,7 +28,7 @@ msgstr "(%{ index } sus %{ length })"
|
|||
|
||||
#: front/src/components/Sidebar.vue:22
|
||||
msgid "(empty)"
|
||||
msgstr "(void)"
|
||||
msgstr "(voida)"
|
||||
|
||||
#: front/src/components/common/ActionTable.vue:43
|
||||
#: front/src/components/common/ActionTable.vue:51
|
||||
|
@ -2364,7 +2364,7 @@ msgstr "Nom d’utilizaire o corrièl"
|
|||
|
||||
#: front/src/components/instance/Stats.vue:13
|
||||
msgid "users"
|
||||
msgstr "utilizaire"
|
||||
msgstr "utilizaires"
|
||||
|
||||
#: front/src/components/Sidebar.vue:103 src/views/admin/Settings.vue:81
|
||||
#: front/src/views/admin/users/Base.vue:5 src/views/admin/users/UsersList.vue:3
|
||||
|
|
|
@ -8,14 +8,15 @@ msgstr ""
|
|||
"Project-Id-Version: front 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-07-22 14:12+0200\n"
|
||||
"PO-Revision-Date: 2018-07-20 19:03+0000\n"
|
||||
"PO-Revision-Date: 2018-07-31 13:32+0000\n"
|
||||
"Last-Translator: Marcin Mikołajczak <me@m4sk.in>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: pl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
|
||||
"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
|
||||
"|| n%100>=20) ? 1 : 2;\n"
|
||||
"X-Generator: Weblate 2.20\n"
|
||||
|
||||
#: front/src/components/playlists/PlaylistModal.vue:9
|
||||
|
@ -669,9 +670,8 @@ msgid "Do you want to delete the playlist \"%{ playlist }\"?"
|
|||
msgstr "Czy chcesz usunąć listę odtwarzania „%{ playlist }”?"
|
||||
|
||||
#: front/src/views/radios/Detail.vue:26
|
||||
#, fuzzy
|
||||
msgid "Do you want to delete the radio \"%{ radio }\"?"
|
||||
msgstr "Czy chcesz usunąć radio „{{ radio }}”?"
|
||||
msgstr "Czy chcesz usunąć radio „%{ radio }”?"
|
||||
|
||||
#: front/src/components/common/ActionTable.vue:29
|
||||
msgid "Do you want to launch %{ action } on %{ count } element?"
|
||||
|
@ -1002,7 +1002,7 @@ msgstr "Uzyskaj odpowiednie metadane"
|
|||
|
||||
#: front/src/App.vue:74
|
||||
msgid "Help us translate Funkwhale"
|
||||
msgstr ""
|
||||
msgstr "Pomóż nam tłumaczyć Funkwhale"
|
||||
|
||||
#: front/src/components/library/Home.vue:65
|
||||
msgid "Home"
|
||||
|
@ -1682,18 +1682,16 @@ msgid "Radio Builder"
|
|||
msgstr "Tworzenie radia"
|
||||
|
||||
#: front/src/components/library/radios/Builder.vue:15
|
||||
#, fuzzy
|
||||
msgid "Radio created"
|
||||
msgstr "Nazwa radia"
|
||||
msgstr "Utworzono radio"
|
||||
|
||||
#: front/src/components/library/radios/Builder.vue:21
|
||||
msgid "Radio name"
|
||||
msgstr "Nazwa radia"
|
||||
|
||||
#: front/src/components/library/radios/Builder.vue:12
|
||||
#, fuzzy
|
||||
msgid "Radio updated"
|
||||
msgstr "Nazwa radia"
|
||||
msgstr "Zaktualizowano radio"
|
||||
|
||||
#: front/src/components/library/Library.vue:10
|
||||
#: src/components/library/Radios.vue:141
|
||||
|
|
|
@ -8,7 +8,7 @@ msgstr ""
|
|||
"Project-Id-Version: front 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-07-17 19:29+0200\n"
|
||||
"PO-Revision-Date: 2018-07-21 21:30+0000\n"
|
||||
"PO-Revision-Date: 2018-07-23 16:49+0000\n"
|
||||
"Last-Translator: Tim Stahel <gitlab@swedneck.xyz>\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: sv\n"
|
||||
|
@ -459,11 +459,11 @@ msgstr ""
|
|||
|
||||
#: front/src/components/auth/Settings.vue:98
|
||||
msgid "Changing your password will have the following consequences"
|
||||
msgstr ""
|
||||
msgstr "Ändring av lösenord har följande konsekvenser"
|
||||
|
||||
#: front/src/App.vue:4
|
||||
msgid "Choose your instance"
|
||||
msgstr ""
|
||||
msgstr "Välj din instans"
|
||||
|
||||
#: front/src/components/Home.vue:64
|
||||
msgid "Clean library"
|
||||
|
|
|
@ -1,124 +1,92 @@
|
|||
{
|
||||
"name": "front",
|
||||
"version": "1.0.0",
|
||||
"description": "Funkwhale front-end",
|
||||
"author": "Eliot Berriot <contact@eliotberriot.com>",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "scripts/i18n-compile.sh && node build/dev-server.js",
|
||||
"start": "scripts/i18n-compile.sh && node build/dev-server.js",
|
||||
"build": "node build/build.js",
|
||||
"serve": "scripts/i18n-compile.sh && vue-cli-service serve --port ${VUE_PORT:-8000} --host ${VUE_HOST:-0.0.0.0}",
|
||||
"build": "scripts/i18n-compile.sh && vue-cli-service build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"i18n-extract": "scripts/i18n-extract.sh",
|
||||
"i18n-compile": "scripts/i18n-compile.sh",
|
||||
"unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
|
||||
"unit-watch": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js",
|
||||
"e2e": "node test/e2e/runner.js",
|
||||
"test": "npm run unit && npm run e2e",
|
||||
"lint": "eslint --ext .js,.vue src test/unit/specs test/e2e/specs"
|
||||
"test:unit": "vue-cli-service test:unit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@panter/vue-i18next": "^0.9.1",
|
||||
"axios": "^0.17.1",
|
||||
"dateformat": "^2.0.0",
|
||||
"axios": "^0.18.0",
|
||||
"dateformat": "^3.0.3",
|
||||
"django-channels": "^1.1.6",
|
||||
"js-logger": "^1.3.0",
|
||||
"howler": "^2.0.14",
|
||||
"js-logger": "^1.4.1",
|
||||
"jwt-decode": "^2.2.0",
|
||||
"lodash": "^4.17.4",
|
||||
"masonry-layout": "^4.2.1",
|
||||
"moment": "^2.20.1",
|
||||
"moxios": "^0.4.0",
|
||||
"raven-js": "^3.22.3",
|
||||
"semantic-ui-css": "^2.2.10",
|
||||
"lodash": "^4.17.10",
|
||||
"masonry-layout": "^4.2.2",
|
||||
"moment": "^2.22.2",
|
||||
"raven-js": "^3.26.4",
|
||||
"semantic-ui-css": "^2.3.3",
|
||||
"showdown": "^1.8.6",
|
||||
"vue": "^2.5.16",
|
||||
"vue-gettext": "^2.0.31",
|
||||
"vue-lazyload": "^1.1.4",
|
||||
"vue-masonry": "^0.10.16",
|
||||
"vue-router": "^2.3.1",
|
||||
"vue-upload-component": "^2.7.4",
|
||||
"vuedraggable": "^2.14.1",
|
||||
"vue": "^2.5.17",
|
||||
"vue-gettext": "^2.1.0",
|
||||
"vue-lazyload": "^1.2.6",
|
||||
"vue-masonry": "^0.11.5",
|
||||
"vue-router": "^3.0.1",
|
||||
"vue-upload-component": "^2.8.11",
|
||||
"vuedraggable": "^2.16.0",
|
||||
"vuex": "^3.0.1",
|
||||
"vuex-persistedstate": "^2.5.2",
|
||||
"vuex-persistedstate": "^2.5.4",
|
||||
"vuex-router-sync": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.7.2",
|
||||
"babel-core": "^6.22.1",
|
||||
"babel-eslint": "^7.1.1",
|
||||
"babel-loader": "7",
|
||||
"babel-plugin-istanbul": "^4.1.1",
|
||||
"babel-plugin-transform-runtime": "^6.22.0",
|
||||
"babel-preset-env": "^1.3.2",
|
||||
"babel-preset-stage-2": "^6.22.0",
|
||||
"babel-register": "^6.22.0",
|
||||
"chai": "^3.5.0",
|
||||
"chalk": "^1.1.3",
|
||||
"chromedriver": "^2.27.2",
|
||||
"connect-history-api-fallback": "^1.3.0",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"cross-env": "^4.0.0",
|
||||
"cross-spawn": "^5.0.1",
|
||||
"css-loader": "^0.28.0",
|
||||
"easygettext": "^2.5.0",
|
||||
"es6-promise": "^4.2.2",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-config-standard": "^6.2.1",
|
||||
"eslint-friendly-formatter": "^2.0.7",
|
||||
"eslint-loader": "^1.7.1",
|
||||
"eslint-plugin-html": "^2.0.0",
|
||||
"eslint-plugin-promise": "^3.4.0",
|
||||
"eslint-plugin-standard": "^2.0.1",
|
||||
"eventsource-polyfill": "^0.9.6",
|
||||
"express": "^4.14.1",
|
||||
"extract-text-webpack-plugin": "^2.0.0",
|
||||
"file-loader": "^0.11.1",
|
||||
"friendly-errors-webpack-plugin": "^1.1.3",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"http-proxy-middleware": "^0.17.3",
|
||||
"inject-loader": "^3.0.0",
|
||||
"karma": "^1.4.1",
|
||||
"karma-coverage": "^1.1.1",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-phantomjs-launcher": "^1.0.2",
|
||||
"karma-phantomjs-shim": "^1.4.0",
|
||||
"karma-sinon-chai": "^1.3.1",
|
||||
"karma-sinon-stub-promise": "^1.0.0",
|
||||
"karma-sourcemap-loader": "^0.3.7",
|
||||
"karma-spec-reporter": "0.0.30",
|
||||
"karma-webpack": "^2.0.2",
|
||||
"lolex": "^1.5.2",
|
||||
"mocha": "^3.2.0",
|
||||
"nightwatch": "^0.9.12",
|
||||
"node-sass": "^4.5.3",
|
||||
"opn": "^4.0.2",
|
||||
"optimize-css-assets-webpack-plugin": "^1.3.0",
|
||||
"ora": "^1.2.0",
|
||||
"phantomjs-prebuilt": "^2.1.14",
|
||||
"rimraf": "^2.6.0",
|
||||
"sass-loader": "^6.0.5",
|
||||
"selenium-server": "^3.0.1",
|
||||
"semver": "^5.3.0",
|
||||
"shelljs": "^0.7.6",
|
||||
"sinon": "^2.1.0",
|
||||
"sinon-chai": "^2.8.0",
|
||||
"sinon-stub-promise": "^4.0.0",
|
||||
"url-loader": "^0.5.8",
|
||||
"vue-loader": "^12.1.0",
|
||||
"vue-style-loader": "^3.0.1",
|
||||
"vue-template-compiler": "^2.3.3",
|
||||
"webpack": "3",
|
||||
"webpack-bundle-analyzer": "^2.2.1",
|
||||
"webpack-dev-middleware": "^1.10.0",
|
||||
"webpack-hot-middleware": "^2.18.0",
|
||||
"webpack-merge": "^4.1.0"
|
||||
"@vue/cli-plugin-babel": "^3.0.0",
|
||||
"@vue/cli-plugin-eslint": "^3.0.0",
|
||||
"@vue/cli-plugin-unit-mocha": "^3.0.0",
|
||||
"@vue/cli-service": "^3.0.0",
|
||||
"@vue/test-utils": "^1.0.0-beta.20",
|
||||
"chai": "^4.1.2",
|
||||
"easygettext": "^2.6.3",
|
||||
"eslint-plugin-html": "^4.0.5",
|
||||
"mocha": "^5.2.0",
|
||||
"moxios": "^0.4.0",
|
||||
"node-sass": "^4.9.3",
|
||||
"sass-loader": "^7.1.0",
|
||||
"sinon": "^6.1.5",
|
||||
"vue-template-compiler": "^2.5.17"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
},
|
||||
"plugins": [
|
||||
"html"
|
||||
],
|
||||
"rules": {
|
||||
"no-console": 0,
|
||||
"no-unused-vars": [
|
||||
2,
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "none"
|
||||
}
|
||||
]
|
||||
},
|
||||
"extends": [
|
||||
"plugin:vue/essential",
|
||||
"eslint:recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not ie <= 8"
|
||||
]
|
||||
],
|
||||
"author": "Eliot Berriot <contact@eliotberriot.com>",
|
||||
"description": "Funkwhale front-end"
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/* This is a custom CSS file that can be loaded thanks to settings.json */
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.png">
|
||||
<title>Funkwhale</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but front doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"additionalStylesheets": ["/custom.css"]
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<div id="app">
|
||||
<!-- here, we display custom stylesheets, if any -->
|
||||
<link v-for="url in customStylesheets" rel="stylesheet" property="stylesheet" :href="url" :key="url">
|
||||
<div class="ui main text container instance-chooser" v-if="!$store.state.instance.instanceUrl">
|
||||
<div class="ui padded segment">
|
||||
<h1 class="ui header"><translate>Choose your instance</translate></h1>
|
||||
|
@ -163,11 +165,7 @@ export default {
|
|||
messages: state => state.ui.messages
|
||||
}),
|
||||
suggestedInstances () {
|
||||
let rootUrl = (
|
||||
window.location.protocol + '//' + window.location.hostname +
|
||||
(window.location.port ? ':' + window.location.port : '')
|
||||
)
|
||||
let instances = [rootUrl, 'https://demo.funkwhale.audio']
|
||||
let instances = [this.$store.getters['instance/defaultUrl'](), 'https://demo.funkwhale.audio']
|
||||
return instances
|
||||
},
|
||||
version () {
|
||||
|
@ -175,6 +173,11 @@ export default {
|
|||
return null
|
||||
}
|
||||
return _.get(this.nodeinfo, 'software.version')
|
||||
},
|
||||
customStylesheets () {
|
||||
if (this.$store.state.instance.frontSettings) {
|
||||
return this.$store.state.instance.frontSettings.additionalStylesheets || []
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -243,7 +246,7 @@ html, body {
|
|||
left: 350px;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
z-index: 1;
|
||||
z-index: 2000;
|
||||
}
|
||||
background-color: white;
|
||||
.item {
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 67 KiB |
|
@ -0,0 +1,5 @@
|
|||
This work is provided under the terms of the Attribution-ShareAlike 4.0 International (CC BY-SA 3.0) license.
|
||||
|
||||
The terms of this license can be found here : https://creativecommons.org/licenses/by-sa/4.0/legalcode.
|
||||
|
||||
The logo, the favicon and its derivatives were designed by Francis Gading.
|
|
@ -84,9 +84,10 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped>
|
||||
.ui.menu {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
.ui.pagination.menu .item {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
@ -191,7 +191,8 @@ export default {
|
|||
backend: backend,
|
||||
tracksChangeBuffer: null,
|
||||
isCollapsed: true,
|
||||
fetchInterval: null
|
||||
fetchInterval: null,
|
||||
showAdmin: this.getShowAdmin()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
@ -220,16 +221,6 @@ export default {
|
|||
pendingFollows
|
||||
}
|
||||
},
|
||||
showAdmin () {
|
||||
let adminPermissions = [
|
||||
this.$store.state.auth.availablePermissions['federation'],
|
||||
this.$store.state.auth.availablePermissions['library'],
|
||||
this.$store.state.auth.availablePermissions['upload']
|
||||
]
|
||||
return adminPermissions.filter(e => {
|
||||
return e
|
||||
}).length > 0
|
||||
},
|
||||
tracks: {
|
||||
get () {
|
||||
return this.$store.state.queue.tracks
|
||||
|
@ -250,6 +241,17 @@ export default {
|
|||
...mapActions({
|
||||
cleanTrack: 'queue/cleanTrack'
|
||||
}),
|
||||
getShowAdmin () {
|
||||
let adminPermissions = [
|
||||
this.$store.state.auth.availablePermissions['federation'],
|
||||
this.$store.state.auth.availablePermissions['library'],
|
||||
this.$store.state.auth.availablePermissions['upload']
|
||||
]
|
||||
return adminPermissions.filter(e => {
|
||||
return e
|
||||
}).length > 0
|
||||
},
|
||||
|
||||
fetchNotificationsCount () {
|
||||
this.$store.dispatch('ui/fetchFederationNotificationsCount')
|
||||
this.$store.dispatch('ui/fetchImportRequestsCount')
|
||||
|
@ -289,6 +291,7 @@ export default {
|
|||
},
|
||||
'$store.state.auth.availablePermissions': {
|
||||
handler () {
|
||||
this.showAdmin = this.getShowAdmin()
|
||||
this.fetchNotificationsCount()
|
||||
},
|
||||
deep: true
|
||||
|
|
|
@ -110,7 +110,7 @@ export default {
|
|||
resolve(self.tracks)
|
||||
} else if (self.playlist) {
|
||||
let url = 'playlists/' + self.playlist.id + '/'
|
||||
axios.get(url + 'tracks').then((response) => {
|
||||
axios.get(url + 'tracks/').then((response) => {
|
||||
resolve(response.data.results.map(plt => {
|
||||
return plt.track
|
||||
}))
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
<template>
|
||||
<div class="ui inverted segment player-wrapper" :style="style">
|
||||
<div class="player">
|
||||
<keep-alive>
|
||||
<audio-track
|
||||
ref="currentAudio"
|
||||
v-if="renderAudio && currentTrack"
|
||||
:is-current="true"
|
||||
:start-time="$store.state.player.currentTime"
|
||||
:autoplay="$store.state.player.playing"
|
||||
:track="currentTrack">
|
||||
</audio-track>
|
||||
</keep-alive>
|
||||
<audio-track
|
||||
ref="currentAudio"
|
||||
v-if="currentTrack"
|
||||
:is-current="true"
|
||||
:start-time="$store.state.player.currentTime"
|
||||
:autoplay="$store.state.player.playing"
|
||||
:key="audioKey"
|
||||
:track="currentTrack">
|
||||
</audio-track>
|
||||
<div v-if="currentTrack" class="track-area ui unstackable items">
|
||||
<div class="ui inverted item">
|
||||
<div class="ui tiny image">
|
||||
|
@ -160,13 +159,13 @@
|
|||
import {mapState, mapGetters, mapActions} from 'vuex'
|
||||
import GlobalEvents from '@/components/utils/global-events'
|
||||
import ColorThief from '@/vendor/color-thief'
|
||||
import {Howl} from 'howler'
|
||||
|
||||
import AudioTrack from '@/components/audio/Track'
|
||||
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
|
||||
import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon'
|
||||
|
||||
export default {
|
||||
name: 'player',
|
||||
components: {
|
||||
TrackFavoriteIcon,
|
||||
TrackPlaylistIcon,
|
||||
|
@ -177,16 +176,28 @@ export default {
|
|||
let defaultAmbiantColors = [[46, 46, 46], [46, 46, 46], [46, 46, 46], [46, 46, 46]]
|
||||
return {
|
||||
isShuffling: false,
|
||||
renderAudio: true,
|
||||
sliderVolume: this.volume,
|
||||
defaultAmbiantColors: defaultAmbiantColors,
|
||||
showVolume: false,
|
||||
ambiantColors: defaultAmbiantColors
|
||||
ambiantColors: defaultAmbiantColors,
|
||||
audioKey: String(new Date()),
|
||||
dummyAudio: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// we trigger the watcher explicitely it does not work otherwise
|
||||
this.sliderVolume = this.volume
|
||||
// this is needed to unlock audio playing under some browsers,
|
||||
// cf https://github.com/goldfire/howler.js#mobilechrome-playback
|
||||
// but we never actually load those audio files
|
||||
this.dummyAudio = new Howl({
|
||||
preload: false,
|
||||
autoplay: false,
|
||||
src: ['noop.webm', 'noop.mp3']
|
||||
})
|
||||
},
|
||||
destroyed () {
|
||||
this.dummyAudio.unload()
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
|
@ -305,21 +316,13 @@ export default {
|
|||
},
|
||||
watch: {
|
||||
currentTrack (newValue) {
|
||||
if (!this.isShuffling) {
|
||||
this.audioKey = String(new Date())
|
||||
}
|
||||
if (!newValue || !newValue.album.cover) {
|
||||
this.ambiantColors = this.defaultAmbiantColors
|
||||
}
|
||||
},
|
||||
currentIndex (newValue, oldValue) {
|
||||
if (newValue !== oldValue) {
|
||||
// why this? to ensure the audio tag is deleted and fully
|
||||
// rerendered, so we don't have any issues with cached position
|
||||
// or whatever
|
||||
this.renderAudio = false
|
||||
this.$nextTick(() => {
|
||||
this.renderAudio = true
|
||||
})
|
||||
}
|
||||
},
|
||||
volume (newValue) {
|
||||
this.sliderVolume = newValue
|
||||
},
|
||||
|
@ -385,9 +388,6 @@ export default {
|
|||
.volume-control {
|
||||
position: relative;
|
||||
width: 12.5% !important;
|
||||
.icon {
|
||||
// margin: 0;
|
||||
}
|
||||
[type="range"] {
|
||||
max-width: 70%;
|
||||
position: absolute;
|
||||
|
@ -395,16 +395,11 @@ export default {
|
|||
left: 25%;
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type=range] {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
input[type=range]:focus {
|
||||
outline: none;
|
||||
}
|
||||
input[type=range]::-webkit-slider-runnable-track {
|
||||
cursor: pointer;
|
||||
background: white;
|
||||
opacity: 0.3;
|
||||
}
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
background: white;
|
||||
|
@ -413,10 +408,6 @@ export default {
|
|||
border-radius: 3px;
|
||||
width: 10px;
|
||||
}
|
||||
input[type=range]:focus::-webkit-slider-runnable-track {
|
||||
background: #white;
|
||||
opacity: 0.3;
|
||||
}
|
||||
input[type=range]::-moz-range-track {
|
||||
cursor: pointer;
|
||||
background: white;
|
||||
|
@ -455,7 +446,7 @@ export default {
|
|||
background: white;
|
||||
}
|
||||
input[type=range]:focus::-ms-fill-upper {
|
||||
background: #white;
|
||||
background: white;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,13 @@
|
|||
<template>
|
||||
<audio
|
||||
ref="audio"
|
||||
@error="errored"
|
||||
@loadeddata="loaded"
|
||||
@durationchange="updateDuration"
|
||||
@timeupdate="updateProgressThrottled"
|
||||
@ended="ended"
|
||||
preload>
|
||||
<source
|
||||
@error="sourceErrored"
|
||||
v-for="src in srcs"
|
||||
:src="src.url"
|
||||
:type="src.type">
|
||||
</audio>
|
||||
<i />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import url from '@/utils/url'
|
||||
import _ from 'lodash'
|
||||
import url from '@/utils/url'
|
||||
import {Howl} from 'howler'
|
||||
|
||||
// import logger from '@/logging'
|
||||
|
||||
export default {
|
||||
|
@ -30,11 +19,44 @@ export default {
|
|||
},
|
||||
data () {
|
||||
return {
|
||||
realTrack: this.track,
|
||||
sourceErrors: 0,
|
||||
isUpdatingTime: false
|
||||
sound: null,
|
||||
isUpdatingTime: false,
|
||||
progressInterval: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let self = this
|
||||
this.sound = new Howl({
|
||||
src: this.srcs.map((s) => { return s.url }),
|
||||
autoplay: false,
|
||||
loop: false,
|
||||
html5: true,
|
||||
preload: true,
|
||||
volume: this.volume,
|
||||
onend: function () {
|
||||
self.ended()
|
||||
},
|
||||
onunlock: function () {
|
||||
if (this.$store.state.player.playing) {
|
||||
self.sound.play()
|
||||
}
|
||||
},
|
||||
onload: function () {
|
||||
self.$store.commit('player/resetErrorCount')
|
||||
self.$store.commit('player/duration', self.sound.duration())
|
||||
}
|
||||
})
|
||||
if (this.autoplay) {
|
||||
this.sound.play()
|
||||
this.$store.commit('player/playing', true)
|
||||
this.observeProgress(true)
|
||||
}
|
||||
},
|
||||
destroyed () {
|
||||
this.observeProgress(false)
|
||||
this.sound.unload()
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
playing: state => state.player.playing,
|
||||
|
@ -44,7 +66,7 @@ export default {
|
|||
looping: state => state.player.looping
|
||||
}),
|
||||
srcs: function () {
|
||||
let file = this.realTrack.files[0]
|
||||
let file = this.track.files[0]
|
||||
if (!file) {
|
||||
this.$store.dispatch('player/trackErrored')
|
||||
return []
|
||||
|
@ -68,90 +90,58 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
errored: function () {
|
||||
let self = this
|
||||
setTimeout(
|
||||
() => { self.$store.dispatch('player/trackErrored') }
|
||||
, 1000)
|
||||
},
|
||||
sourceErrored: function () {
|
||||
this.sourceErrors += 1
|
||||
if (this.sourceErrors >= this.srcs.length) {
|
||||
// all sources failed
|
||||
this.errored()
|
||||
}
|
||||
},
|
||||
updateDuration: function (e) {
|
||||
if (!this.$refs.audio) {
|
||||
return
|
||||
}
|
||||
this.$store.commit('player/duration', this.$refs.audio.duration)
|
||||
},
|
||||
loaded: function () {
|
||||
if (!this.$refs.audio) {
|
||||
return
|
||||
}
|
||||
this.$refs.audio.volume = this.volume
|
||||
this.$store.commit('player/resetErrorCount')
|
||||
if (this.isCurrent) {
|
||||
this.$store.commit('player/duration', this.$refs.audio.duration)
|
||||
if (this.startTime) {
|
||||
this.setCurrentTime(this.startTime)
|
||||
}
|
||||
if (this.autoplay) {
|
||||
this.$store.commit('player/playing', true)
|
||||
this.$refs.audio.play()
|
||||
}
|
||||
}
|
||||
},
|
||||
updateProgress: function () {
|
||||
this.isUpdatingTime = true
|
||||
if (this.$refs.audio) {
|
||||
this.$store.dispatch('player/updateProgress', this.$refs.audio.currentTime)
|
||||
if (this.sound && this.sound.state() === 'loaded') {
|
||||
this.$store.dispatch('player/updateProgress', this.sound.seek())
|
||||
}
|
||||
},
|
||||
ended: function () {
|
||||
let onlyTrack = this.$store.state.queue.tracks.length === 1
|
||||
if (this.looping === 1 || (onlyTrack && this.looping === 2)) {
|
||||
this.setCurrentTime(0)
|
||||
this.$refs.audio.play()
|
||||
observeProgress: function (enable) {
|
||||
let self = this
|
||||
if (enable) {
|
||||
if (self.progressInterval) {
|
||||
clearInterval(self.progressInterval)
|
||||
}
|
||||
self.progressInterval = setInterval(() => {
|
||||
self.updateProgress()
|
||||
}, 1000)
|
||||
} else {
|
||||
this.$store.dispatch('player/trackEnded', this.realTrack)
|
||||
clearInterval(self.progressInterval)
|
||||
}
|
||||
},
|
||||
setCurrentTime (t) {
|
||||
if (t < 0 | t > this.duration) {
|
||||
return
|
||||
}
|
||||
if (t === this.$refs.audio.currentTime) {
|
||||
if (t === this.sound.seek()) {
|
||||
return
|
||||
}
|
||||
if (t === 0) {
|
||||
this.updateProgressThrottled.cancel()
|
||||
}
|
||||
this.$refs.audio.currentTime = t
|
||||
this.sound.seek(t)
|
||||
},
|
||||
ended: function () {
|
||||
let onlyTrack = this.$store.state.queue.tracks.length === 1
|
||||
if (this.looping === 1 || (onlyTrack && this.looping === 2)) {
|
||||
this.sound.seek(0)
|
||||
this.sound.play()
|
||||
} else {
|
||||
this.$store.dispatch('player/trackEnded', this.track)
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
track: _.debounce(function (newValue) {
|
||||
this.realTrack = newValue
|
||||
this.setCurrentTime(0)
|
||||
this.$refs.audio.load()
|
||||
}, 1000, {leading: true, trailing: true}),
|
||||
playing: function (newValue) {
|
||||
if (newValue === true) {
|
||||
this.$refs.audio.play()
|
||||
this.sound.play()
|
||||
} else {
|
||||
this.$refs.audio.pause()
|
||||
}
|
||||
},
|
||||
'$store.state.queue.currentIndex' () {
|
||||
if (this.$store.state.player.playing) {
|
||||
this.$refs.audio.play()
|
||||
this.sound.pause()
|
||||
}
|
||||
this.observeProgress(newValue)
|
||||
},
|
||||
volume: function (newValue) {
|
||||
this.$refs.audio.volume = newValue
|
||||
this.sound.volume(newValue)
|
||||
},
|
||||
currentTime (newValue) {
|
||||
if (!this.isUpdatingTime) {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
{{ track.title }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td colspan="6">
|
||||
<td colspan="4">
|
||||
<router-link v-if="track.artist.id === albumArtist.id" class="artist discrete link" :to="{name: 'library.artists.detail', params: {id: track.artist.id }}">
|
||||
{{ track.artist.name }}
|
||||
</router-link>
|
||||
|
@ -29,11 +29,17 @@
|
|||
</router-link>
|
||||
</template>
|
||||
</td>
|
||||
<td colspan="6">
|
||||
<td colspan="4">
|
||||
<router-link class="album discrete link" :to="{name: 'library.albums.detail', params: {id: track.album.id }}">
|
||||
{{ track.album.title }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td colspan="4" v-if="file && file.duration">
|
||||
{{ time.parse(file.duration) }}
|
||||
</td>
|
||||
<td colspan="4" v-else>
|
||||
<translate>N/A</translate>
|
||||
</td>
|
||||
<td>
|
||||
<track-favorite-icon class="favorite-icon" :track="track"></track-favorite-icon>
|
||||
<track-playlist-icon
|
||||
|
@ -44,6 +50,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import time from '@/utils/time'
|
||||
import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
|
||||
import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon'
|
||||
import PlayButton from '@/components/audio/PlayButton'
|
||||
|
@ -59,6 +67,11 @@ export default {
|
|||
TrackPlaylistIcon,
|
||||
PlayButton
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
time
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
albumArtist () {
|
||||
if (this.artist) {
|
||||
|
@ -66,6 +79,9 @@ export default {
|
|||
} else {
|
||||
return this.track.album.artist
|
||||
}
|
||||
},
|
||||
file () {
|
||||
return this.track.files[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
<th></th>
|
||||
<th></th>
|
||||
<th colspan="6"><translate>Title</translate></th>
|
||||
<th colspan="6"><translate>Artist</translate></th>
|
||||
<th colspan="6"><translate>Album</translate></th>
|
||||
<th colspan="4"><translate>Artist</translate></th>
|
||||
<th colspan="4"><translate>Album</translate></th>
|
||||
<th colspan="4"><translate>Duration</translate></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -10,9 +10,6 @@
|
|||
<i @click="fetchData(url)" :class="['ui', 'circular', 'medium', 'refresh', 'icon']">
|
||||
</i>
|
||||
<div class="ui divided unstackable items">
|
||||
<div v-if="isLoading" class="ui inverted active dimmer">
|
||||
<div class="ui loader"></div>
|
||||
</div>
|
||||
<div class="item" v-for="object in objects" :key="object.id">
|
||||
<div class="ui tiny image">
|
||||
<img v-if="object.track.album.cover.original" v-lazy="$store.getters['instance/absoluteUrl'](object.track.album.cover.medium_square_crop)">
|
||||
|
@ -45,6 +42,9 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isLoading" class="ui inverted active dimmer">
|
||||
<div class="ui loader"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -126,4 +126,7 @@ export default {
|
|||
.refresh.icon {
|
||||
float: right;
|
||||
}
|
||||
.ui.divided.items > .item:last-child {
|
||||
padding-bottom: 1em !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
<div class="ui basic green label">
|
||||
<translate>This is you!</translate>
|
||||
</div>
|
||||
<div v-if="profile.is_staff" class="ui yellow label">
|
||||
<a v-if="profile.is_staff"
|
||||
class="ui yellow label"
|
||||
:href="$store.getters['instance/absoluteUrl']('/api/admin')"
|
||||
target="_blank">
|
||||
<i class="star icon"></i>
|
||||
<translate>Staff member</translate>
|
||||
</div>
|
||||
</a>
|
||||
<router-link class="ui tiny basic button" :to="{path: '/settings'}">
|
||||
<i class="setting icon"></i>
|
||||
<translate>Settings...</translate>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<div v-if="isLoading" class="ui vertical segment" v-title="labels.title">
|
||||
<div v-title="labels.title">
|
||||
<div v-if="isLoading" class="ui vertical segment">
|
||||
<div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
|
||||
</div>
|
||||
<template v-if="artist">
|
||||
|
@ -102,7 +102,7 @@ export default {
|
|||
self.artist = response.data
|
||||
self.isLoading = false
|
||||
self.isLoadingAlbums = true
|
||||
axios.get('albums/', {params: {artist: this.id, ordering: '-release_date'}}).then((response) => {
|
||||
axios.get('albums/', {params: {artist: self.id, ordering: '-release_date'}}).then((response) => {
|
||||
let parsed = JSON.parse(JSON.stringify(response.data.results))
|
||||
self.albums = parsed.map((album) => {
|
||||
return backend.Album.clean(album)
|
||||
|
@ -158,7 +158,7 @@ export default {
|
|||
})[0]
|
||||
},
|
||||
headerStyle () {
|
||||
if (!this.cover.original) {
|
||||
if (!this.cover || !this.cover.original) {
|
||||
return ''
|
||||
}
|
||||
return 'background-image: url(' + this.$store.getters['instance/absoluteUrl'](this.cover.original) + ')'
|
||||
|
|
|
@ -54,7 +54,7 @@ export default {
|
|||
}
|
||||
&.with-background {
|
||||
.header {
|
||||
&, .sub, a {
|
||||
&, .sub {
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.8);
|
||||
color: white !important;
|
||||
}
|
||||
|
|
|
@ -87,6 +87,17 @@
|
|||
<translate>N/A</translate>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<translate>Type</translate>
|
||||
</td>
|
||||
<td v-if="file.mimetype">
|
||||
{{ file.mimetype }}
|
||||
</td>
|
||||
<td v-else>
|
||||
<translate>N/A</translate>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -17,12 +17,16 @@
|
|||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline fields">
|
||||
<div class="">
|
||||
<div class="field">
|
||||
<label for="name"><translate>Radio name</translate></label>
|
||||
<input id="name" type="text" v-model="radioName" :placeholder="labels.placeholder" />
|
||||
<input id="name" type="text" v-model="radioName" :placeholder="labels.placeholder.name" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="description"><translate>Description</translate></label>
|
||||
<textarea rows="2" id="description" type="text" v-model="radioDesc" :placeholder="labels.placeholder.description" />
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<input id="public" type="checkbox" v-model="isPublic" />
|
||||
<label for="public"><translate>Display publicly</translate></label>
|
||||
</div>
|
||||
|
@ -113,6 +117,7 @@ export default {
|
|||
filters: [],
|
||||
checkResult: null,
|
||||
radioName: '',
|
||||
radioDesc: '',
|
||||
isPublic: true
|
||||
}
|
||||
},
|
||||
|
@ -164,6 +169,7 @@ export default {
|
|||
}
|
||||
})
|
||||
self.radioName = response.data.name
|
||||
self.radioDesc = response.data.description
|
||||
self.isPublic = response.data.is_public
|
||||
self.isLoading = false
|
||||
})
|
||||
|
@ -197,6 +203,7 @@ export default {
|
|||
})
|
||||
final = {
|
||||
'name': this.radioName,
|
||||
'description': this.radioDesc,
|
||||
'is_public': this.isPublic,
|
||||
'config': final
|
||||
}
|
||||
|
@ -224,7 +231,10 @@ export default {
|
|||
computed: {
|
||||
labels () {
|
||||
let title = this.$gettext('Radio Builder')
|
||||
let placeholder = this.$gettext('My awesome radio')
|
||||
let placeholder = {
|
||||
'name': this.$gettext('My awesome radio'),
|
||||
'description': this.$gettext('My awesome description')
|
||||
}
|
||||
return {
|
||||
title,
|
||||
placeholder
|
||||
|
|
|
@ -57,8 +57,8 @@
|
|||
<td>
|
||||
<human-date :date="scope.obj.creation_date"></human-date>
|
||||
</td>
|
||||
<td v-if="scope.obj.audio_mimetype">
|
||||
{{ scope.obj.audio_mimetype }}
|
||||
<td v-if="scope.obj.mimetype">
|
||||
{{ scope.obj.mimetype }}
|
||||
</td>
|
||||
<td v-else>
|
||||
<translate>N/A</translate>
|
||||
|
|
|
@ -14,13 +14,14 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="extra content">
|
||||
<user-link :user="radio.user" class="left floated" />
|
||||
<radio-button class="right floated button" :type="type" :custom-radio-id="customRadioId"></radio-button>
|
||||
<router-link
|
||||
class="ui basic yellow button"
|
||||
v-if="$store.state.auth.authenticated && type === 'custom' && customRadio.user === $store.state.auth.profile.id"
|
||||
class="ui basic yellow button right floated"
|
||||
v-if="$store.state.auth.authenticated && type === 'custom' && radio.user.id === $store.state.auth.profile.id"
|
||||
:to="{name: 'library.radios.edit', params: {id: customRadioId }}">
|
||||
<translate>Edit...</translate>
|
||||
</router-link>
|
||||
<radio-button class="right floated button" :type="type" :custom-radio-id="customRadioId"></radio-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -12,6 +12,14 @@ export default {
|
|||
"code": "eo",
|
||||
"label": "Esperanto"
|
||||
},
|
||||
{
|
||||
"code": "de",
|
||||
"label": "Deutsch"
|
||||
},
|
||||
{
|
||||
"code": "es",
|
||||
"label": "Español"
|
||||
},
|
||||
{
|
||||
"code": "fr_FR",
|
||||
"label": "Français"
|
||||
|
@ -27,6 +35,10 @@ export default {
|
|||
{
|
||||
"code": "pl",
|
||||
"label": "Polszczyzna"
|
||||
},
|
||||
{
|
||||
"code": "pt_PT",
|
||||
"label": "Português (Portugal)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -43,6 +43,10 @@ if (availableLanguages[store.state.ui.currentLanguage]) {
|
|||
Vue.use(GetTextPlugin, {
|
||||
availableLanguages: availableLanguages,
|
||||
defaultLanguage: defaultLanguage,
|
||||
// cf https://github.com/Polyconseil/vue-gettext#configuration
|
||||
// not recommended but this is fixing weird bugs with translation nodes
|
||||
// not being updated when in v-if/v-else clauses
|
||||
autoAddKeyAttributes: true,
|
||||
languageVmMixin: {
|
||||
computed: {
|
||||
currentKebabCase: function () {
|
||||
|
@ -126,6 +130,8 @@ axios.interceptors.response.use(function (response) {
|
|||
return Promise.reject(error)
|
||||
})
|
||||
|
||||
store.dispatch('instance/fetchFrontSettings')
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
el: '#app',
|
||||
|
|
|
@ -100,7 +100,7 @@ export default new Router({
|
|||
name: 'signup',
|
||||
component: Signup,
|
||||
props: (route) => ({
|
||||
invitation: route.query.invitation
|
||||
defaultInvitation: route.query.invitation
|
||||
})
|
||||
},
|
||||
{
|
||||
|
|
|
@ -56,13 +56,20 @@ export default {
|
|||
fetch ({dispatch, state, commit, rootState}, url) {
|
||||
// will fetch favorites by batches from API to have them locally
|
||||
let params = {
|
||||
user: rootState.auth.profile.id
|
||||
user: rootState.auth.profile.id,
|
||||
page_size: 50,
|
||||
ordering: '-creation_date'
|
||||
}
|
||||
url = url || 'favorites/tracks/'
|
||||
return axios.get(url, {params: params}).then((response) => {
|
||||
let promise
|
||||
if (url) {
|
||||
promise = axios.get(url)
|
||||
} else {
|
||||
promise = axios.get('favorites/tracks/', {params: params})
|
||||
}
|
||||
return promise.then((response) => {
|
||||
logger.default.info('Fetched a batch of ' + response.data.results.length + ' favorites')
|
||||
response.data.results.forEach(result => {
|
||||
commit('track', {id: result.track, value: true})
|
||||
commit('track', {id: result.track.id, value: true})
|
||||
})
|
||||
if (response.data.next) {
|
||||
dispatch('fetch', response.data.next)
|
||||
|
|
|
@ -2,10 +2,18 @@ import axios from 'axios'
|
|||
import logger from '@/logging'
|
||||
import _ from 'lodash'
|
||||
|
||||
function getDefaultUrl () {
|
||||
return (
|
||||
window.location.protocol + '//' + window.location.hostname +
|
||||
(window.location.port ? ':' + window.location.port : '')
|
||||
)
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
maxEvents: 200,
|
||||
frontSettings: {},
|
||||
instanceUrl: process.env.INSTANCE_URL,
|
||||
events: [],
|
||||
settings: {
|
||||
|
@ -53,6 +61,9 @@ export default {
|
|||
events: (state, value) => {
|
||||
state.events = value
|
||||
},
|
||||
frontSettings: (state, value) => {
|
||||
state.frontSettings = value
|
||||
},
|
||||
instanceUrl: (state, value) => {
|
||||
if (value && !value.endsWith('/')) {
|
||||
value = value + '/'
|
||||
|
@ -67,6 +78,9 @@ export default {
|
|||
}
|
||||
},
|
||||
getters: {
|
||||
defaultUrl: (state) => () => {
|
||||
return getDefaultUrl()
|
||||
},
|
||||
absoluteUrl: (state) => (relativeUrl) => {
|
||||
if (relativeUrl.startsWith('http')) {
|
||||
return relativeUrl
|
||||
|
@ -74,7 +88,9 @@ export default {
|
|||
if (state.instanceUrl.endsWith('/') && relativeUrl.startsWith('/')) {
|
||||
relativeUrl = relativeUrl.slice(1)
|
||||
}
|
||||
return state.instanceUrl + relativeUrl
|
||||
|
||||
let instanceUrl = state.instanceUrl || getDefaultUrl()
|
||||
return instanceUrl + relativeUrl
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
@ -110,6 +126,13 @@ export default {
|
|||
}, response => {
|
||||
logger.default.error('Error while fetching settings', response.data)
|
||||
})
|
||||
},
|
||||
fetchFrontSettings ({commit}) {
|
||||
return axios.get('/settings.json').then(response => {
|
||||
commit('frontSettings', response.data)
|
||||
}, response => {
|
||||
logger.default.error('Error when fetching front-end configuration (or no customization available)')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -32,3 +32,12 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.ui.menu .item > .label {
|
||||
position: absolute;
|
||||
right: -2em;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -50,3 +50,12 @@ export default {
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.ui.menu .item > .label {
|
||||
position: absolute;
|
||||
right: -2em;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -24,13 +24,13 @@
|
|||
<play-button class="orange" :tracks="tracks"><translate>Play all</translate></play-button>
|
||||
<button
|
||||
class="ui icon button"
|
||||
v-if="playlist.user.id === $store.state.auth.profile.id"
|
||||
v-if="$store.state.auth.profile && playlist.user.id === $store.state.auth.profile.id"
|
||||
@click="edit = !edit">
|
||||
<i class="pencil icon"></i>
|
||||
<template v-if="edit"><translate>End edition</translate></template>
|
||||
<template v-else><translate>Edit...</translate></template>
|
||||
</button>
|
||||
<dangerous-button v-if="playlist.user.id === $store.state.auth.profile.id" class="labeled icon" :action="deletePlaylist">
|
||||
<dangerous-button v-if="$store.state.auth.profile && playlist.user.id === $store.state.auth.profile.id" class="labeled icon" :action="deletePlaylist">
|
||||
<i class="trash icon"></i> <translate>Delete</translate>
|
||||
<p slot="modal-header">
|
||||
<translate :translate-params="{playlist: playlist.name}">Do you want to delete the playlist "%{ playlist }"?</translate>
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 991 B |
|
@ -1,26 +0,0 @@
|
|||
// A custom Nightwatch assertion.
|
||||
// the name of the method is the filename.
|
||||
// can be used in tests like this:
|
||||
//
|
||||
// browser.assert.elementCount(selector, count)
|
||||
//
|
||||
// for how to write custom assertions see
|
||||
// http://nightwatchjs.org/guide#writing-custom-assertions
|
||||
exports.assertion = function (selector, count) {
|
||||
this.message = 'Testing if element <' + selector + '> has count: ' + count
|
||||
this.expected = count
|
||||
this.pass = function (val) {
|
||||
return val === this.expected
|
||||
}
|
||||
this.value = function (res) {
|
||||
return res.value
|
||||
}
|
||||
this.command = function (cb) {
|
||||
var self = this
|
||||
return this.api.execute(function (selector) {
|
||||
return document.querySelectorAll(selector).length
|
||||
}, [selector], function (res) {
|
||||
cb.call(self, res)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
require('babel-register')
|
||||
var config = require('../../config')
|
||||
|
||||
// http://nightwatchjs.org/gettingstarted#settings-file
|
||||
module.exports = {
|
||||
src_folders: ['test/e2e/specs'],
|
||||
output_folder: 'test/e2e/reports',
|
||||
custom_assertions_path: ['test/e2e/custom-assertions'],
|
||||
|
||||
selenium: {
|
||||
start_process: true,
|
||||
server_path: require('selenium-server').path,
|
||||
host: '127.0.0.1',
|
||||
port: 4444,
|
||||
cli_args: {
|
||||
'webdriver.chrome.driver': require('chromedriver').path
|
||||
}
|
||||
},
|
||||
|
||||
test_settings: {
|
||||
default: {
|
||||
selenium_port: 4444,
|
||||
selenium_host: 'localhost',
|
||||
silent: true,
|
||||
globals: {
|
||||
devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
|
||||
}
|
||||
},
|
||||
|
||||
chrome: {
|
||||
desiredCapabilities: {
|
||||
browserName: 'chrome',
|
||||
javascriptEnabled: true,
|
||||
acceptSslCerts: true
|
||||
}
|
||||
},
|
||||
|
||||
firefox: {
|
||||
desiredCapabilities: {
|
||||
browserName: 'firefox',
|
||||
javascriptEnabled: true,
|
||||
acceptSslCerts: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
// 1. start the dev server using production config
|
||||
process.env.NODE_ENV = 'testing'
|
||||
var server = require('../../build/dev-server.js')
|
||||
|
||||
server.ready.then(() => {
|
||||
// 2. run the nightwatch test suite against it
|
||||
// to run in additional browsers:
|
||||
// 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
|
||||
// 2. add it to the --env flag below
|
||||
// or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
|
||||
// For more information on Nightwatch's config file, see
|
||||
// http://nightwatchjs.org/guide#settings-file
|
||||
var opts = process.argv.slice(2)
|
||||
if (opts.indexOf('--config') === -1) {
|
||||
opts = opts.concat(['--config', 'test/e2e/nightwatch.conf.js'])
|
||||
}
|
||||
if (opts.indexOf('--env') === -1) {
|
||||
opts = opts.concat(['--env', 'chrome'])
|
||||
}
|
||||
|
||||
var spawn = require('cross-spawn')
|
||||
var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
|
||||
|
||||
runner.on('exit', function (code) {
|
||||
server.close()
|
||||
process.exit(code)
|
||||
})
|
||||
|
||||
runner.on('error', function (err) {
|
||||
server.close()
|
||||
throw err
|
||||
})
|
||||
})
|
|
@ -1,19 +0,0 @@
|
|||
// For authoring Nightwatch tests, see
|
||||
// http://nightwatchjs.org/guide#usage
|
||||
|
||||
module.exports = {
|
||||
'default e2e tests': function (browser) {
|
||||
// automatically uses dev Server port from /config.index.js
|
||||
// default: http://localhost:8080
|
||||
// see nightwatch.conf.js
|
||||
const devServer = browser.globals.devServerURL
|
||||
|
||||
browser
|
||||
.url(devServer)
|
||||
.waitForElementVisible('#app', 5000)
|
||||
.assert.elementPresent('.hello')
|
||||
.assert.containsText('h1', 'Welcome to Your Vue.js App')
|
||||
.assert.elementCount('img', 1)
|
||||
.end()
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
// require all test files (files that ends with .spec.js)
|
||||
const testsContext = require.context('./specs', true, /\.spec$/)
|
||||
testsContext.keys().forEach(testsContext)
|
||||
|
||||
// require all src files except main.js for coverage.
|
||||
// you can also change this to match only the subset of files that
|
||||
// you want coverage for.
|
||||
const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/)
|
||||
srcContext.keys().forEach(srcContext)
|
|
@ -1,38 +0,0 @@
|
|||
// This is a karma config file. For more details see
|
||||
// http://karma-runner.github.io/0.13/config/configuration-file.html
|
||||
// we are also using it with karma-webpack
|
||||
// https://github.com/webpack/karma-webpack
|
||||
|
||||
var webpackConfig = require('../../build/webpack.test.conf')
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
// to run in additional browsers:
|
||||
// 1. install corresponding karma launcher
|
||||
// http://karma-runner.github.io/0.13/config/browsers.html
|
||||
// 2. add it to the `browsers` array below.
|
||||
browsers: ['PhantomJS'],
|
||||
frameworks: ['mocha', 'sinon-stub-promise', 'sinon-chai', 'phantomjs-shim'],
|
||||
reporters: ['spec', 'coverage'],
|
||||
files: [
|
||||
'../../node_modules/es6-promise/dist/es6-promise.auto.js',
|
||||
'./index.js'
|
||||
],
|
||||
preprocessors: {
|
||||
'./index.js': ['webpack', 'sourcemap']
|
||||
},
|
||||
captureTimeout: 15000,
|
||||
retryLimit: 1,
|
||||
webpack: webpackConfig,
|
||||
webpackMiddleware: {
|
||||
noInfo: true
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: './coverage',
|
||||
reporters: [
|
||||
{ type: 'lcov', subdir: '.' },
|
||||
{ type: 'text-summary' }
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
import {expect} from 'chai'
|
||||
|
||||
import Username from '@/components/common/Username.vue'
|
||||
|
||||
import { render } from '../../utils'
|
|
@ -1,3 +1,5 @@
|
|||
import {expect} from 'chai'
|
||||
|
||||
import {truncate, markdown, ago, capitalize, year} from '@/filters'
|
||||
|
||||
describe('filters', () => {
|
|
@ -1,4 +1,6 @@
|
|||
var sinon = require('sinon')
|
||||
import {expect} from 'chai'
|
||||
|
||||
import moxios from 'moxios'
|
||||
import store from '@/store/auth'
|
||||
|
||||
|
@ -8,7 +10,7 @@ describe('store/auth', () => {
|
|||
var sandbox
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.sandbox.create()
|
||||
sandbox = sinon.createSandbox()
|
||||
moxios.install()
|
||||
})
|
||||
afterEach(function () {
|
||||
|
@ -84,7 +86,7 @@ describe('store/auth', () => {
|
|||
})
|
||||
})
|
||||
describe('actions', () => {
|
||||
it('logout', (done) => {
|
||||
it('logout', () => {
|
||||
testAction({
|
||||
action: store.actions.logout,
|
||||
params: {state: {}},
|
||||
|
@ -96,18 +98,18 @@ describe('store/auth', () => {
|
|||
{ type: 'queue/reset', payload: null, options: {root: true} },
|
||||
{ type: 'radios/reset', payload: null, options: {root: true} }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
it('check jwt null', (done) => {
|
||||
it('check jwt null', () => {
|
||||
testAction({
|
||||
action: store.actions.check,
|
||||
params: {state: {}},
|
||||
expectedMutations: [
|
||||
{ type: 'authenticated', payload: false }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
it('check jwt set', (done) => {
|
||||
it('check jwt set', () => {
|
||||
testAction({
|
||||
action: store.actions.check,
|
||||
params: {state: {token: 'test', username: 'user'}},
|
||||
|
@ -118,9 +120,9 @@ describe('store/auth', () => {
|
|||
{ type: 'fetchProfile' },
|
||||
{ type: 'refreshToken' }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
it('login success', (done) => {
|
||||
it('login success', () => {
|
||||
moxios.stubRequest('token/', {
|
||||
status: 200,
|
||||
response: {
|
||||
|
@ -139,9 +141,9 @@ describe('store/auth', () => {
|
|||
expectedActions: [
|
||||
{ type: 'fetchProfile' }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
it('login error', (done) => {
|
||||
it('login error', () => {
|
||||
moxios.stubRequest('token/', {
|
||||
status: 500,
|
||||
response: {
|
||||
|
@ -160,7 +162,7 @@ describe('store/auth', () => {
|
|||
done()
|
||||
})
|
||||
})
|
||||
it('fetchProfile', (done) => {
|
||||
it('fetchProfile', () => {
|
||||
const profile = {
|
||||
username: 'bob',
|
||||
permissions: {
|
||||
|
@ -183,9 +185,9 @@ describe('store/auth', () => {
|
|||
{ type: 'favorites/fetch', payload: null, options: {root: true} },
|
||||
{ type: 'playlists/fetchOwn', payload: null, options: {root: true} }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
it('refreshToken', (done) => {
|
||||
it('refreshToken', () => {
|
||||
moxios.stubRequest('token/refresh/', {
|
||||
status: 200,
|
||||
response: {token: 'newtoken'}
|
||||
|
@ -196,7 +198,7 @@ describe('store/auth', () => {
|
|||
expectedMutations: [
|
||||
{ type: 'token', payload: 'newtoken' }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,3 +1,5 @@
|
|||
import {expect} from 'chai'
|
||||
|
||||
import store from '@/store/favorites'
|
||||
|
||||
import { testAction } from '../../utils'
|
||||
|
@ -28,7 +30,7 @@ describe('store/favorites', () => {
|
|||
})
|
||||
})
|
||||
describe('actions', () => {
|
||||
it('toggle true', (done) => {
|
||||
it('toggle true', () => {
|
||||
testAction({
|
||||
action: store.actions.toggle,
|
||||
payload: 1,
|
||||
|
@ -36,9 +38,9 @@ describe('store/favorites', () => {
|
|||
expectedActions: [
|
||||
{ type: 'set', payload: {id: 1, value: true} }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
it('toggle true', (done) => {
|
||||
it('toggle true', () => {
|
||||
testAction({
|
||||
action: store.actions.toggle,
|
||||
payload: 1,
|
||||
|
@ -46,7 +48,7 @@ describe('store/favorites', () => {
|
|||
expectedActions: [
|
||||
{ type: 'set', payload: {id: 1, value: false} }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,3 +1,4 @@
|
|||
import {expect} from 'chai'
|
||||
var sinon = require('sinon')
|
||||
import moxios from 'moxios'
|
||||
import store from '@/store/instance'
|
||||
|
@ -7,7 +8,7 @@ describe('store/instance', () => {
|
|||
var sandbox
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.sandbox.create()
|
||||
sandbox = sinon.createSandbox()
|
||||
moxios.install()
|
||||
})
|
||||
afterEach(function () {
|
||||
|
@ -26,7 +27,7 @@ describe('store/instance', () => {
|
|||
})
|
||||
})
|
||||
describe('actions', () => {
|
||||
it('fetchSettings', (done) => {
|
||||
it('fetchSettings', () => {
|
||||
moxios.stubRequest('instance/settings/', {
|
||||
status: 200,
|
||||
response: [
|
||||
|
@ -64,7 +65,7 @@ describe('store/instance', () => {
|
|||
}
|
||||
}
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,3 +1,5 @@
|
|||
import {expect} from 'chai'
|
||||
|
||||
import store from '@/store/player'
|
||||
|
||||
import { testAction } from '../../utils'
|
||||
|
@ -100,7 +102,7 @@ describe('store/player', () => {
|
|||
})
|
||||
})
|
||||
describe('actions', () => {
|
||||
it('incrementVolume', (done) => {
|
||||
it('incrementVolume', () => {
|
||||
testAction({
|
||||
action: store.actions.incrementVolume,
|
||||
payload: 0.2,
|
||||
|
@ -108,27 +110,27 @@ describe('store/player', () => {
|
|||
expectedMutations: [
|
||||
{ type: 'volume', payload: 0.7 + 0.2 }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
it('toggle play false', (done) => {
|
||||
it('toggle play false', () => {
|
||||
testAction({
|
||||
action: store.actions.togglePlay,
|
||||
params: {state: {playing: false}},
|
||||
expectedMutations: [
|
||||
{ type: 'playing', payload: true }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
it('toggle play true', (done) => {
|
||||
it('toggle play true', () => {
|
||||
testAction({
|
||||
action: store.actions.togglePlay,
|
||||
params: {state: {playing: true}},
|
||||
expectedMutations: [
|
||||
{ type: 'playing', payload: false }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
it('trackEnded', (done) => {
|
||||
it('trackEnded', () => {
|
||||
testAction({
|
||||
action: store.actions.trackEnded,
|
||||
payload: {test: 'track'},
|
||||
|
@ -137,9 +139,9 @@ describe('store/player', () => {
|
|||
{ type: 'trackListened', payload: {test: 'track'} },
|
||||
{ type: 'queue/next', payload: null, options: {root: true} }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
it('trackEnded calls populateQueue if last', (done) => {
|
||||
it('trackEnded calls populateQueue if last', () => {
|
||||
testAction({
|
||||
action: store.actions.trackEnded,
|
||||
payload: {test: 'track'},
|
||||
|
@ -149,9 +151,9 @@ describe('store/player', () => {
|
|||
{ type: 'radios/populateQueue', payload: null, options: {root: true} },
|
||||
{ type: 'queue/next', payload: null, options: {root: true} }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
it('trackErrored', (done) => {
|
||||
it('trackErrored', () => {
|
||||
testAction({
|
||||
action: store.actions.trackErrored,
|
||||
payload: {test: 'track'},
|
||||
|
@ -163,16 +165,16 @@ describe('store/player', () => {
|
|||
expectedActions: [
|
||||
{ type: 'queue/next', payload: null, options: {root: true} }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
it('updateProgress', (done) => {
|
||||
it('updateProgress', () => {
|
||||
testAction({
|
||||
action: store.actions.updateProgress,
|
||||
payload: 1,
|
||||
expectedMutations: [
|
||||
{ type: 'currentTime', payload: 1 }
|
||||
]
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,3 +1,4 @@
|
|||
import {expect} from 'chai'
|
||||
var sinon = require('sinon')
|
||||
import moxios from 'moxios'
|
||||
import store from '@/store/playlists'
|
||||
|
@ -8,7 +9,7 @@ describe('store/playlists', () => {
|
|||
var sandbox
|
||||
|
||||
beforeEach(function () {
|
||||
sandbox = sinon.sandbox.create()
|
||||
sandbox = sinon.createSandbox()
|
||||
moxios.install()
|
||||
})
|
||||
afterEach(function () {
|
||||
|
@ -24,13 +25,13 @@ describe('store/playlists', () => {
|
|||
})
|
||||
})
|
||||
describe('actions', () => {
|
||||
it('fetchOwn does nothing with no user', (done) => {
|
||||
it('fetchOwn does nothing with no user', () => {
|
||||
testAction({
|
||||
action: store.actions.fetchOwn,
|
||||
payload: null,
|
||||
params: {state: { playlists: [] }, rootState: {auth: {profile: {}}}},
|
||||
expectedMutations: []
|
||||
}, done)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue