From 8ee9a536e1833ac9a39f0593b9ef35e4e6ef5280 Mon Sep 17 00:00:00 2001
From: Georg Krause <mail@georg-krause.net>
Date: Mon, 6 Dec 2021 11:35:20 +0100
Subject: [PATCH] Lint the frontend code

---
 .gitlab-ci.yml                                |   5 +-
 front/.eslintrc.js                            |   2 +
 front/package.json                            |   2 +-
 front/src/EmbedFrame.vue                      | 426 +++++++-----
 front/src/audio/backend.js                    |   4 +-
 front/src/audio/volume.js                     |  28 +-
 front/src/components/Footer.vue               | 217 ++++--
 front/src/components/Home.vue                 | 314 ++++++---
 front/src/components/Logo.vue                 |  47 +-
 front/src/components/LogoText.vue             |  17 +-
 front/src/components/PageNotFound.vue         |  36 +-
 front/src/components/Pagination.vue           |  65 +-
 front/src/components/Queue.vue                | 397 +++++++----
 front/src/components/RemoteSearchForm.vue     | 215 +++---
 front/src/components/ServiceMessages.vue      |   8 +-
 front/src/components/SetInstanceModal.vue     | 184 ++++--
 front/src/components/ShortcutsModal.vue       |  69 +-
 front/src/components/admin/SettingsGroup.vue  | 192 ++++--
 .../components/admin/SignupFormBuilder.vue    | 159 +++--
 front/src/components/audio/ArtistLabel.vue    |  25 +-
 front/src/components/audio/ChannelCard.vue    |  73 ++-
 front/src/components/audio/ChannelEntries.vue |  78 ++-
 .../src/components/audio/ChannelEntryCard.vue |  85 ++-
 front/src/components/audio/ChannelForm.vue    | 299 ++++++---
 .../src/components/audio/ChannelSerieCard.vue |  57 +-
 front/src/components/audio/ChannelSeries.vue  |  70 +-
 front/src/components/audio/ChannelsWidget.vue |  50 +-
 front/src/components/audio/EmbedWizard.vue    |  97 ++-
 .../components/audio/LibraryFollowButton.vue  |  33 +-
 front/src/components/audio/PlayButton.vue     | 189 ++++--
 front/src/components/audio/Player.vue         | 495 ++++++++------
 front/src/components/audio/Search.vue         |  90 ++-
 front/src/components/audio/SearchBar.vue      |  97 +--
 front/src/components/audio/VolumeControl.vue  |  61 +-
 front/src/components/audio/album/Card.vue     |  45 +-
 front/src/components/audio/album/Widget.vue   |  95 ++-
 front/src/components/audio/artist/Card.vue    |  63 +-
 front/src/components/audio/artist/Widget.vue  |  90 ++-
 .../components/audio/podcast/MobileRow.vue    | 115 ++--
 front/src/components/audio/podcast/Row.vue    |  99 +--
 front/src/components/audio/podcast/Table.vue  |  85 +--
 .../src/components/audio/track/MobileRow.vue  |  96 ++-
 .../components/audio/track/PlayIndicator.vue  |  10 +-
 front/src/components/audio/track/Row.vue      | 141 ++--
 front/src/components/audio/track/Table.vue    | 194 +++---
 front/src/components/audio/track/Widget.vue   | 160 +++--
 front/src/components/auth/ApplicationEdit.vue |  84 ++-
 front/src/components/auth/ApplicationForm.vue | 183 ++++--
 front/src/components/auth/ApplicationNew.vue  |  36 +-
 front/src/components/auth/Authorize.vue       | 216 ++++--
 front/src/components/auth/LoginForm.vue       | 122 ++--
 front/src/components/auth/Logout.vue          |  52 +-
 front/src/components/auth/Plugin.vue          | 195 ++++--
 front/src/components/auth/SignupForm.vue      | 227 ++++---
 .../src/components/auth/SubsonicTokenForm.vue | 187 ++++--
 front/src/components/channels/AlbumForm.vue   |  61 +-
 front/src/components/channels/AlbumModal.vue  |  44 +-
 front/src/components/channels/AlbumSelect.vue |  57 +-
 .../src/components/channels/LicenseSelect.vue |  57 +-
 .../components/channels/SubscribeButton.vue   |  57 +-
 front/src/components/channels/UploadForm.vue  | 397 ++++++-----
 .../channels/UploadMetadataForm.vue           |  46 +-
 front/src/components/channels/UploadModal.vue | 151 ++++-
 .../src/components/common/ActionFeedback.vue  |  37 +-
 front/src/components/common/ActionTable.vue   | 338 ++++++----
 front/src/components/common/ActorAvatar.vue   |  17 +-
 front/src/components/common/ActorLink.vue     |  26 +-
 front/src/components/common/AjaxButton.vue    |  15 +-
 .../src/components/common/AttachmentInput.vue | 114 +++-
 front/src/components/common/CollapseLink.vue  |  22 +-
 front/src/components/common/ContentForm.vue   | 123 ++--
 front/src/components/common/CopyInput.vue     |  39 +-
 .../src/components/common/DangerousButton.vue |  43 +-
 front/src/components/common/Duration.vue      |  16 +-
 front/src/components/common/EmptyState.vue    |  13 +-
 front/src/components/common/ExpandableDiv.vue |  24 +-
 front/src/components/common/HumanDate.vue     |  16 +-
 front/src/components/common/HumanDuration.vue |   7 +-
 .../src/components/common/InlineSearchBar.vue |  39 +-
 front/src/components/common/LoginModal.vue    |  95 +--
 front/src/components/common/Message.vue       |  20 +-
 .../components/common/RenderedDescription.vue | 117 ++--
 front/src/components/common/Tooltip.vue       |   7 +-
 front/src/components/common/UserLink.vue      |  17 +-
 front/src/components/common/UserModal.vue     |  12 +-
 front/src/components/common/Username.vue      |   2 +-
 front/src/components/favorites/List.vue       | 207 +++---
 .../favorites/TrackFavoriteIcon.vue           |  37 +-
 .../src/components/federation/FetchButton.vue | 202 ++++--
 .../components/federation/LibraryWidget.vue   |  71 +-
 front/src/components/forms/PasswordInput.vue  |  39 +-
 front/src/components/globals.js               |  38 +-
 front/src/components/library/AlbumBase.vue    | 325 ++++++---
 front/src/components/library/AlbumDetail.vue  |  92 ++-
 .../src/components/library/AlbumDropdown.vue  | 136 ++--
 front/src/components/library/AlbumEdit.vue    |  47 +-
 front/src/components/library/Albums.vue       | 218 +++---
 front/src/components/library/ArtistBase.vue   | 325 +++++----
 front/src/components/library/ArtistDetail.vue | 126 +++-
 front/src/components/library/ArtistEdit.vue   |  47 +-
 front/src/components/library/Artists.vue      | 253 ++++---
 front/src/components/library/EditCard.vue     | 196 ++++--
 front/src/components/library/EditDetail.vue   |  32 +-
 front/src/components/library/EditForm.vue     | 263 +++++---
 front/src/components/library/EditList.vue     |  75 ++-
 front/src/components/library/FileUpload.vue   | 620 +++++++++++-------
 .../components/library/FileUploadWidget.vue   |  18 +-
 front/src/components/library/FsBrowser.vue    |  46 +-
 front/src/components/library/FsLogs.vue       |  22 +-
 front/src/components/library/Home.vue         | 116 ++--
 .../components/library/ImportStatusModal.vue  | 131 ++--
 front/src/components/library/Library.vue      |  12 +-
 front/src/components/library/Podcasts.vue     | 319 +++++----
 front/src/components/library/Radios.vue       | 234 ++++---
 front/src/components/library/TagDetail.vue    | 105 ++-
 front/src/components/library/TagsSelector.vue |  72 +-
 front/src/components/library/TrackBase.vue    | 367 +++++++----
 front/src/components/library/TrackDetail.vue  | 279 +++++---
 front/src/components/library/TrackEdit.vue    |  62 +-
 front/src/components/library/UploadDetail.vue |  24 +-
 .../src/components/library/radios/Builder.vue | 298 ++++++---
 .../src/components/library/radios/Filter.vue  | 117 ++--
 front/src/components/manage/ChannelsTable.vue | 229 +++++--
 .../components/manage/library/AlbumsTable.vue | 225 +++++--
 .../manage/library/ArtistsTable.vue           | 232 ++++---
 .../manage/library/EditsCardList.vue          | 179 +++--
 .../manage/library/LibrariesTable.vue         | 248 ++++---
 .../components/manage/library/TagsTable.vue   | 182 +++--
 .../components/manage/library/TracksTable.vue | 238 ++++---
 .../manage/library/UploadsTable.vue           | 358 +++++++---
 .../manage/moderation/AccountsTable.vue       | 210 ++++--
 .../manage/moderation/DomainsTable.vue        | 227 +++++--
 .../manage/moderation/InstancePolicyCard.vue  |  92 ++-
 .../manage/moderation/InstancePolicyForm.vue  | 238 +++++--
 .../manage/moderation/InstancePolicyModal.vue |  90 ++-
 .../components/manage/moderation/NoteForm.vue |  60 +-
 .../manage/moderation/NotesThread.vue         |  56 +-
 .../manage/moderation/ReportCard.vue          | 342 +++++++---
 .../manage/moderation/UserRequestCard.vue     | 189 ++++--
 .../manage/users/InvitationForm.vue           |  85 ++-
 .../manage/users/InvitationsTable.vue         | 194 ++++--
 .../components/manage/users/UsersTable.vue    | 247 ++++---
 front/src/components/mixins/Ordering.vue      |  20 +-
 front/src/components/mixins/Pagination.vue    |   4 +-
 front/src/components/mixins/PlayOptions.vue   |  70 +-
 front/src/components/mixins/Report.vue        |  39 +-
 front/src/components/mixins/SmartSearch.vue   |  64 +-
 front/src/components/mixins/Translations.vue  |  60 +-
 .../src/components/moderation/FilterModal.vue |  84 ++-
 .../moderation/ReportCategoryDropdown.vue     |  41 +-
 .../src/components/moderation/ReportModal.vue | 195 ++++--
 .../notifications/NotificationRow.vue         | 107 ++-
 front/src/components/playlists/Card.vue       |  51 +-
 front/src/components/playlists/CardList.vue   |   8 +-
 front/src/components/playlists/Editor.vue     | 269 +++++---
 front/src/components/playlists/Form.vue       | 112 +++-
 .../components/playlists/PlaylistModal.vue    | 246 ++++---
 .../playlists/TrackPlaylistIcon.vue           |  24 +-
 front/src/components/playlists/Widget.vue     |  86 ++-
 front/src/components/radios/Button.vue        |  54 +-
 front/src/components/radios/Card.vue          |  70 +-
 front/src/components/tags/List.vue            |  35 +-
 front/src/components/utils/global-events.vue  |   6 +-
 front/src/edits.js                            |  32 +-
 front/src/embed.js                            |   4 +-
 front/src/entities.js                         |  52 +-
 front/src/filters.js                          |  40 +-
 front/src/lodash.js                           |   2 +-
 front/src/main.js                             |  61 +-
 front/src/radios.js                           |  18 +-
 front/src/registerServiceWorker.js            |  12 +-
 front/src/sanitize.js                         |  66 +-
 front/src/search.js                           |  94 +--
 front/src/service-worker.js                   |  59 +-
 front/src/store/auth.js                       |   2 +-
 front/src/store/channels.js                   |  28 +-
 front/src/store/favorites.js                  |  28 +-
 front/src/store/index.js                      |   6 +-
 front/src/store/instance.js                   |  32 +-
 front/src/store/libraries.js                  |  34 +-
 front/src/store/moderation.js                 |  38 +-
 front/src/store/playlists.js                  |   7 +-
 front/src/store/queue.js                      |  52 +-
 front/src/store/radios.js                     |  28 +-
 front/src/store/ui.js                         | 207 +++---
 front/src/utils.js                            |  30 +-
 front/src/utils/color.js                      |   6 +-
 front/src/utils/time.js                       |   2 +-
 front/src/utils/url.js                        |   4 +-
 front/src/views/Notifications.vue             | 274 +++++---
 front/src/views/Search.vue                    | 273 +++++---
 front/src/views/admin/ChannelDetail.vue       | 304 ++++++---
 front/src/views/admin/ChannelsList.vue        |  17 +-
 front/src/views/admin/Settings.vue            | 213 +++---
 front/src/views/admin/library/AlbumDetail.vue | 299 ++++++---
 front/src/views/admin/library/AlbumsList.vue  |  17 +-
 .../src/views/admin/library/ArtistDetail.vue  | 305 ++++++---
 front/src/views/admin/library/ArtistsList.vue |  17 +-
 front/src/views/admin/library/Base.vue        |  75 ++-
 front/src/views/admin/library/EditsList.vue   |  21 +-
 .../src/views/admin/library/LibrariesList.vue |  17 +-
 .../src/views/admin/library/LibraryDetail.vue | 263 +++++---
 front/src/views/admin/library/TagDetail.vue   | 172 +++--
 front/src/views/admin/library/TagsList.vue    |  17 +-
 front/src/views/admin/library/TrackDetail.vue | 320 ++++++---
 front/src/views/admin/library/TracksList.vue  |  17 +-
 .../src/views/admin/library/UploadDetail.vue  | 256 +++++---
 front/src/views/admin/library/UploadsList.vue |  17 +-
 .../views/admin/moderation/AccountsDetail.vue | 475 +++++++++-----
 .../views/admin/moderation/AccountsList.vue   |  21 +-
 front/src/views/admin/moderation/Base.vue     |  75 ++-
 .../views/admin/moderation/DomainsDetail.vue  | 357 ++++++----
 .../views/admin/moderation/DomainsList.vue    |  79 ++-
 .../views/admin/moderation/ReportDetail.vue   |  34 +-
 .../views/admin/moderation/ReportsList.vue    | 203 +++---
 .../views/admin/moderation/RequestDetail.vue  |  34 +-
 .../views/admin/moderation/RequestsList.vue   | 166 +++--
 front/src/views/admin/users/Base.vue          |  31 +-
 .../src/views/admin/users/InvitationsList.vue |  16 +-
 front/src/views/admin/users/UsersList.vue     |  12 +-
 front/src/views/auth/Callback.vue             |  13 +-
 front/src/views/auth/EmailConfirm.vue         |  89 ++-
 front/src/views/auth/Login.vue                |  33 +-
 front/src/views/auth/PasswordReset.vue        |  88 ++-
 front/src/views/auth/PasswordResetConfirm.vue | 101 ++-
 front/src/views/auth/Plugins.vue              |  42 +-
 front/src/views/auth/ProfileActivity.vue      |  59 +-
 front/src/views/auth/ProfileBase.vue          | 185 ++++--
 front/src/views/auth/ProfileOverview.vue      | 135 +++-
 front/src/views/auth/Signup.vue               |  40 +-
 front/src/views/channels/DetailBase.vue       | 512 ++++++++++-----
 front/src/views/channels/DetailEpisodes.vue   |  15 +-
 front/src/views/channels/DetailOverview.vue   | 215 +++---
 .../src/views/channels/SubscriptionsList.vue  |  86 ++-
 front/src/views/content/Base.vue              |  33 +-
 front/src/views/content/Home.vue              |  86 ++-
 front/src/views/content/libraries/Card.vue    |  69 +-
 front/src/views/content/libraries/Files.vue   |   8 +-
 .../views/content/libraries/FilesTable.vue    | 337 ++++++----
 front/src/views/content/libraries/Form.vue    | 122 +++-
 front/src/views/content/libraries/Home.vue    |  95 ++-
 front/src/views/content/libraries/Quota.vue   | 199 ++++--
 front/src/views/content/remote/Card.vue       | 259 +++++---
 front/src/views/content/remote/Home.vue       |  82 ++-
 front/src/views/content/remote/ScanForm.vue   |  60 +-
 front/src/views/library/DetailAlbums.vue      |  28 +-
 front/src/views/library/DetailBase.vue        | 269 +++++---
 front/src/views/library/DetailOverview.vue    |  35 +-
 front/src/views/library/DetailTracks.vue      |  26 +-
 front/src/views/library/Edit.vue              | 128 +++-
 front/src/views/library/Upload.vue            |  21 +-
 front/src/views/playlists/Detail.vue          | 225 ++++---
 front/src/views/playlists/List.vue            | 203 ++++--
 front/src/views/radios/Detail.vue             | 155 +++--
 254 files changed, 19326 insertions(+), 9915 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a3a493e33..380f359ef 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -136,10 +136,7 @@ eslint:
     - cd front
     - yarn install
   script:
-    # We search for all files ending with .vue or .js in src which changed in relation to develop
-    # and lint them. This way we focus on some errors instead of checking the hole repository
-    - export changedFiles=$(git diff --relative --name-only --diff-filter=d origin/develop -- src/ | grep -E "\.(vue|js)$")
-    - yarn run eslint --quiet -f table $(echo $changedFiles | tr '\n' ' ')
+    - yarn lint
   cache:
     key: "$CI_PROJECT_ID__eslint_npm_cache"
     paths:
diff --git a/front/.eslintrc.js b/front/.eslintrc.js
index c01b98750..c07080821 100644
--- a/front/.eslintrc.js
+++ b/front/.eslintrc.js
@@ -20,5 +20,7 @@ module.exports = {
     'vue'
   ],
   rules: {
+    "vue/no-v-html": "off", // TODO: tackle this properly
+    "vue/no-use-v-if-with-v-for": "off"
   }
 }
diff --git a/front/package.json b/front/package.json
index 791dd97f4..75089cc75 100644
--- a/front/package.json
+++ b/front/package.json
@@ -8,7 +8,7 @@
     "serve": "[ ! -d src/translations ] && npm run i18n-compile; vue-cli-service serve --port ${VUE_PORT:-8080} --host ${VUE_HOST:-0.0.0.0}",
     "build": "scripts/i18n-compile.sh && vue-cli-service build",
     "test:unit": "vue-cli-service test:unit --reporter mocha-junit-reporter",
-    "lint": "eslint $(git status --porcelain --untracked-files=no | grep -E '(A|M) ' | cut -d' ' -f3 | sed s_front/__ | grep -E '.(js|vue)$')",
+    "lint": "eslint --ext .js,.vue src",
     "i18n-compile": "scripts/i18n-compile.sh",
     "i18n-extract": "scripts/i18n-extract.sh",
     "fix-fomantic-css": "scripts/fix-fomantic-css.sh",
diff --git a/front/src/EmbedFrame.vue b/front/src/EmbedFrame.vue
index 8d0949b4f..bc31d51e4 100644
--- a/front/src/EmbedFrame.vue
+++ b/front/src/EmbedFrame.vue
@@ -2,57 +2,110 @@
 <template>
   <main :class="[theme]">
     <!-- SVG from https://cdn.plyr.io/3.4.7/plyr.svg -->
-    <svg aria-hidden="true" style="display: none" xmlns="http://www.w3.org/2000/svg">
-      <symbol id="plyr-download"><path d="M9 13c.3 0 .5-.1.7-.3L15.4 7 14 5.6l-4 4V1H8v8.6l-4-4L2.6 7l5.7 5.7c.2.2.4.3.7.3zM2 15h14v2H2z"/></symbol>
-      <symbol id="plyr-enter-fullscreen"><path d="M10 3h3.6l-4 4L11 8.4l4-4V8h2V1h-7zM7 9.6l-4 4V10H1v7h7v-2H4.4l4-4z"/></symbol>
-      <symbol id="plyr-exit-fullscreen"><path d="M1 12h3.6l-4 4L2 17.4l4-4V17h2v-7H1zM16 .6l-4 4V1h-2v7h7V6h-3.6l4-4z"/></symbol>
-      <symbol id="plyr-fast-forward"><path d="M7.875 7.171L0 1v16l7.875-6.171V17L18 9 7.875 1z"/></symbol>
-      <symbol id="plyr-muted"><path d="M12.4 12.5l2.1-2.1 2.1 2.1 1.4-1.4L15.9 9 18 6.9l-1.4-1.4-2.1 2.1-2.1-2.1L11 6.9 13.1 9 11 11.1zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z"/></symbol>
-      <symbol id="plyr-pause"><path d="M6 1H3c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1zM12 1c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1h-3z"/></symbol>
-      <symbol id="plyr-pip"><path d="M13.293 3.293L7.022 9.564l1.414 1.414 6.271-6.271L17 7V1h-6z"/><path d="M13 15H3V5h5V3H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-6h-2v5z"/></symbol>
-      <symbol id="plyr-play"><path d="M15.562 8.1L3.87.225C3.052-.337 2 .225 2 1.125v15.75c0 .9 1.052 1.462 1.87.9L15.563 9.9c.584-.45.584-1.35 0-1.8z"/></symbol>
-      <symbol id="plyr-restart"><path d="M9.7 1.2l.7 6.4 2.1-2.1c1.9 1.9 1.9 5.1 0 7-.9 1-2.2 1.5-3.5 1.5-1.3 0-2.6-.5-3.5-1.5-1.9-1.9-1.9-5.1 0-7 .6-.6 1.4-1.1 2.3-1.3l-.6-1.9C6 2.6 4.9 3.2 4 4.1 1.3 6.8 1.3 11.2 4 14c1.3 1.3 3.1 2 4.9 2 1.9 0 3.6-.7 4.9-2 2.7-2.7 2.7-7.1 0-9.9L16 1.9l-6.3-.7z"/></symbol>
-      <symbol id="plyr-rewind"><path d="M10.125 1L0 9l10.125 8v-6.171L18 17V1l-7.875 6.171z"/></symbol>
-      <symbol id="plyr-settings"><path d="M16.135 7.784a2 2 0 0 1-1.23-2.969c.322-.536.225-.998-.094-1.316l-.31-.31c-.318-.318-.78-.415-1.316-.094a2 2 0 0 1-2.969-1.23C10.065 1.258 9.669 1 9.219 1h-.438c-.45 0-.845.258-.997.865a2 2 0 0 1-2.969 1.23c-.536-.322-.999-.225-1.317.093l-.31.31c-.318.318-.415.781-.093 1.317a2 2 0 0 1-1.23 2.969C1.26 7.935 1 8.33 1 8.781v.438c0 .45.258.845.865.997a2 2 0 0 1 1.23 2.969c-.322.536-.225.998.094 1.316l.31.31c.319.319.782.415 1.316.094a2 2 0 0 1 2.969 1.23c.151.607.547.865.997.865h.438c.45 0 .845-.258.997-.865a2 2 0 0 1 2.969-1.23c.535.321.997.225 1.316-.094l.31-.31c.318-.318.415-.781.094-1.316a2 2 0 0 1 1.23-2.969c.607-.151.865-.547.865-.997v-.438c0-.451-.26-.846-.865-.997zM9 12a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></symbol>
-      <symbol id="plyr-volume"><path d="M15.6 3.3c-.4-.4-1-.4-1.4 0-.4.4-.4 1 0 1.4C15.4 5.9 16 7.4 16 9c0 1.6-.6 3.1-1.8 4.3-.4.4-.4 1 0 1.4.2.2.5.3.7.3.3 0 .5-.1.7-.3C17.1 13.2 18 11.2 18 9s-.9-4.2-2.4-5.7z"/><path d="M11.282 5.282a.909.909 0 0 0 0 1.316c.735.735.995 1.458.995 2.402 0 .936-.425 1.917-.995 2.487a.909.909 0 0 0 0 1.316c.145.145.636.262 1.018.156a.725.725 0 0 0 .298-.156C13.773 11.733 14.13 10.16 14.13 9c0-.17-.002-.34-.011-.51-.053-.992-.319-2.005-1.522-3.208a.909.909 0 0 0-1.316 0zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z"/></symbol>
+    <svg
+      aria-hidden="true"
+      style="display: none"
+      xmlns="http://www.w3.org/2000/svg"
+    >
+      <symbol id="plyr-download"><path d="M9 13c.3 0 .5-.1.7-.3L15.4 7 14 5.6l-4 4V1H8v8.6l-4-4L2.6 7l5.7 5.7c.2.2.4.3.7.3zM2 15h14v2H2z" /></symbol>
+      <symbol id="plyr-enter-fullscreen"><path d="M10 3h3.6l-4 4L11 8.4l4-4V8h2V1h-7zM7 9.6l-4 4V10H1v7h7v-2H4.4l4-4z" /></symbol>
+      <symbol id="plyr-exit-fullscreen"><path d="M1 12h3.6l-4 4L2 17.4l4-4V17h2v-7H1zM16 .6l-4 4V1h-2v7h7V6h-3.6l4-4z" /></symbol>
+      <symbol id="plyr-fast-forward"><path d="M7.875 7.171L0 1v16l7.875-6.171V17L18 9 7.875 1z" /></symbol>
+      <symbol id="plyr-muted"><path d="M12.4 12.5l2.1-2.1 2.1 2.1 1.4-1.4L15.9 9 18 6.9l-1.4-1.4-2.1 2.1-2.1-2.1L11 6.9 13.1 9 11 11.1zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z" /></symbol>
+      <symbol id="plyr-pause"><path d="M6 1H3c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1zM12 1c-.6 0-1 .4-1 1v14c0 .6.4 1 1 1h3c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1h-3z" /></symbol>
+      <symbol id="plyr-pip"><path d="M13.293 3.293L7.022 9.564l1.414 1.414 6.271-6.271L17 7V1h-6z" /><path d="M13 15H3V5h5V3H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-6h-2v5z" /></symbol>
+      <symbol id="plyr-play"><path d="M15.562 8.1L3.87.225C3.052-.337 2 .225 2 1.125v15.75c0 .9 1.052 1.462 1.87.9L15.563 9.9c.584-.45.584-1.35 0-1.8z" /></symbol>
+      <symbol id="plyr-restart"><path d="M9.7 1.2l.7 6.4 2.1-2.1c1.9 1.9 1.9 5.1 0 7-.9 1-2.2 1.5-3.5 1.5-1.3 0-2.6-.5-3.5-1.5-1.9-1.9-1.9-5.1 0-7 .6-.6 1.4-1.1 2.3-1.3l-.6-1.9C6 2.6 4.9 3.2 4 4.1 1.3 6.8 1.3 11.2 4 14c1.3 1.3 3.1 2 4.9 2 1.9 0 3.6-.7 4.9-2 2.7-2.7 2.7-7.1 0-9.9L16 1.9l-6.3-.7z" /></symbol>
+      <symbol id="plyr-rewind"><path d="M10.125 1L0 9l10.125 8v-6.171L18 17V1l-7.875 6.171z" /></symbol>
+      <symbol id="plyr-settings"><path d="M16.135 7.784a2 2 0 0 1-1.23-2.969c.322-.536.225-.998-.094-1.316l-.31-.31c-.318-.318-.78-.415-1.316-.094a2 2 0 0 1-2.969-1.23C10.065 1.258 9.669 1 9.219 1h-.438c-.45 0-.845.258-.997.865a2 2 0 0 1-2.969 1.23c-.536-.322-.999-.225-1.317.093l-.31.31c-.318.318-.415.781-.093 1.317a2 2 0 0 1-1.23 2.969C1.26 7.935 1 8.33 1 8.781v.438c0 .45.258.845.865.997a2 2 0 0 1 1.23 2.969c-.322.536-.225.998.094 1.316l.31.31c.319.319.782.415 1.316.094a2 2 0 0 1 2.969 1.23c.151.607.547.865.997.865h.438c.45 0 .845-.258.997-.865a2 2 0 0 1 2.969-1.23c.535.321.997.225 1.316-.094l.31-.31c.318-.318.415-.781.094-1.316a2 2 0 0 1 1.23-2.969c.607-.151.865-.547.865-.997v-.438c0-.451-.26-.846-.865-.997zM9 12a3 3 0 1 1 0-6 3 3 0 0 1 0 6z" /></symbol>
+      <symbol id="plyr-volume"><path d="M15.6 3.3c-.4-.4-1-.4-1.4 0-.4.4-.4 1 0 1.4C15.4 5.9 16 7.4 16 9c0 1.6-.6 3.1-1.8 4.3-.4.4-.4 1 0 1.4.2.2.5.3.7.3.3 0 .5-.1.7-.3C17.1 13.2 18 11.2 18 9s-.9-4.2-2.4-5.7z" /><path d="M11.282 5.282a.909.909 0 0 0 0 1.316c.735.735.995 1.458.995 2.402 0 .936-.425 1.917-.995 2.487a.909.909 0 0 0 0 1.316c.145.145.636.262 1.018.156a.725.725 0 0 0 .298-.156C13.773 11.733 14.13 10.16 14.13 9c0-.17-.002-.34-.011-.51-.053-.992-.319-2.005-1.522-3.208a.909.909 0 0 0-1.316 0zM3.786 6.008H.714C.286 6.008 0 6.31 0 6.76v4.512c0 .452.286.752.714.752h3.072l4.071 3.858c.5.3 1.143 0 1.143-.602V2.752c0-.601-.643-.977-1.143-.601L3.786 6.008z" /></symbol>
       <!-- those ones are from fork-awesome -->
-      <symbol id="plyr-step-backward"><path d="M979 141c25-25 45-16 45 19v1472c0 35-20 44-45 19L269 941c-6-6-10-12-13-19v678c0 35-29 64-64 64H64c-35 0-64-29-64-64V192c0-35 29-64 64-64h128c35 0 64 29 64 64v678c3-7 7-13 13-19z"/></symbol>
-      <symbol id="plyr-step-forward"><path d="M45 1651c-25 25-45 16-45-19V160c0-35 20-44 45-19l710 710c6 6 10 12 13 19V192c0-35 29-64 64-64h128c35 0 64 29 64 64v1408c0 35-29 64-64 64H832c-35 0-64-29-64-64V922c-3 7-7 13-13 19z"/></symbol>
+      <symbol id="plyr-step-backward"><path d="M979 141c25-25 45-16 45 19v1472c0 35-20 44-45 19L269 941c-6-6-10-12-13-19v678c0 35-29 64-64 64H64c-35 0-64-29-64-64V192c0-35 29-64 64-64h128c35 0 64 29 64 64v678c3-7 7-13 13-19z" /></symbol>
+      <symbol id="plyr-step-forward"><path d="M45 1651c-25 25-45 16-45-19V160c0-35 20-44 45-19l710 710c6 6 10 12 13 19V192c0-35 29-64 64-64h128c35 0 64 29 64 64v1408c0 35-29 64-64 64H832c-35 0-64-29-64-64V922c-3 7-7 13-13 19z" /></symbol>
     </svg>
     <article>
-      <aside class="cover main" v-if="currentTrack">
-        <img height="120" v-if="currentTrack.cover" :src="currentTrack.cover" alt="Cover" />
-        <img height="120" v-else src="./assets/embed/default-cover.jpeg" alt="Cover" />
+      <aside
+        v-if="currentTrack"
+        class="cover main"
+      >
+        <img
+          v-if="currentTrack.cover"
+          height="120"
+          :src="currentTrack.cover"
+          alt="Cover"
+        >
+        <img
+          v-else
+          height="120"
+          src="./assets/embed/default-cover.jpeg"
+          alt="Cover"
+        >
       </aside>
-      <div class="content" aria-label="Track information">
+      <div
+        class="content"
+        aria-label="Track information"
+      >
         <header v-if="currentTrack">
-          <h3><a :href="fullUrl('/library/tracks/' + currentTrack.id)" target="_blank" rel="noopener noreferrer">{{ currentTrack.title }}</a></h3>
-          <a :href="fullUrl('/library/artists/' + currentTrack.artist.id)" target="_blank" rel="noopener noreferrer">{{ currentTrack.artist.name }}</a>
+          <h3>
+            <a
+              :href="fullUrl('/library/tracks/' + currentTrack.id)"
+              target="_blank"
+              rel="noopener noreferrer"
+            >{{ currentTrack.title }}</a>
+          </h3>
+          <a
+            :href="fullUrl('/library/artists/' + currentTrack.artist.id)"
+            target="_blank"
+            rel="noopener noreferrer"
+          >{{ currentTrack.artist.name }}</a>
         </header>
-        <section v-if="!isLoading" class="controls" aria-label="Audio player">
+        <section
+          v-if="!isLoading"
+          class="controls"
+          aria-label="Audio player"
+        >
           <template v-if="currentTrack && currentTrack.sources.length > 0">
-            <div class="queue-controls plyr--audio" v-if="tracks.length > 1">
+            <div
+              v-if="tracks.length > 1"
+              class="queue-controls plyr--audio"
+            >
               <div class="plyr__controls">
                 <button
+                  type="button"
+                  class="plyr__control"
+                  aria-label="Play previous track"
                   @focus="setControlFocus($event, true)"
                   @blur="setControlFocus($event, false)"
                   @click="previous()"
-                  type="button"
-                  class="plyr__control"
-                  aria-label="Play previous track">
-                  <svg class="icon--not-pressed" role="presentation" focusable="false" viewBox="0 0 1100 1650" width="80" height="80">
-                    <use xlink:href="#plyr-step-backward"></use>
+                >
+                  <svg
+                    class="icon--not-pressed"
+                    role="presentation"
+                    focusable="false"
+                    viewBox="0 0 1100 1650"
+                    width="80"
+                    height="80"
+                  >
+                    <use xlink:href="#plyr-step-backward" />
                   </svg>
                 </button>
                 <button
+                  type="button"
+                  class="plyr__control"
+                  aria-label="Play next track"
                   @click="next()"
                   @focus="setControlFocus($event, true)"
                   @blur="setControlFocus($event, false)"
-                  type="button"
-                  class="plyr__control"
-                  aria-label="Play next track">
-                  <svg class="icon--not-pressed" role="presentation" focusable="false" viewBox="0 0 1100 1650" width="80" height="80">
-                    <use xlink:href="#plyr-step-forward"></use>
+                >
+                  <svg
+                    class="icon--not-pressed"
+                    role="presentation"
+                    focusable="false"
+                    viewBox="0 0 1100 1650"
+                    width="80"
+                    height="80"
+                  >
+                    <use xlink:href="#plyr-step-forward" />
                   </svg>
                 </button>
               </div>
@@ -62,51 +115,122 @@
               :key="currentIndex"
               ref="player"
               class="player"
-              :options="{loadSprite: false, controls: controls, duration: currentTrack.sources[0].duration, autoplay}">
+              :options="{loadSprite: false, controls: controls, duration: currentTrack.sources[0].duration, autoplay}"
+            >
               <audio preload="none">
-                <source v-for="source in currentTrack.sources" :src="source.src" :type="source.type"/>
+                <source
+                  v-for="(source, key) in currentTrack.sources"
+                  :key="key"
+                  :src="source.src"
+                  :type="source.type"
+                >
               </audio>
             </vue-plyr>
           </template>
-          <div v-else class="player">
-            <span v-if="error === 'invalid_type'" class="error">Widget improperly configured (bad resource type {{ type }}).</span>
-            <span v-else-if="error === 'invalid_id'" class="error">Widget improperly configured (missing resource id).</span>
-            <span v-else-if="error === 'server_not_found'" class="error">Track not found.</span>
-            <span v-else-if="error === 'server_requires_auth'" class="error">You need to login to access this resource.</span>
-            <span v-else-if="error === 'server_error'" class="error">A server error occurred.</span>
-            <span v-else-if="error === 'server_error'" class="error">An unknown error occurred while loading track data from server.</span>
-            <span v-else-if="currentTrack && currentTrack.sources.length === 0" class="error">This track is unavailable.</span>
-            <span v-else class="error">An unknown error occurred while loading track data.</span>
+          <div
+            v-else
+            class="player"
+          >
+            <span
+              v-if="error === 'invalid_type'"
+              class="error"
+            >Widget improperly configured (bad resource type {{ type }}).</span>
+            <span
+              v-else-if="error === 'invalid_id'"
+              class="error"
+            >Widget improperly configured (missing resource id).</span>
+            <span
+              v-else-if="error === 'server_not_found'"
+              class="error"
+            >Track not found.</span>
+            <span
+              v-else-if="error === 'server_requires_auth'"
+              class="error"
+            >You need to login to access this resource.</span>
+            <span
+              v-else-if="error === 'server_error'"
+              class="error"
+            >An unknown error occurred while loading track data from server.</span>
+            <span
+              v-else-if="currentTrack && currentTrack.sources.length === 0"
+              class="error"
+            >This track is unavailable.</span>
+            <span
+              v-else
+              class="error"
+            >An unknown error occurred while loading track data.</span>
           </div>
-          <a title="Funkwhale" href="https://funkwhale.audio" target="_blank" rel="noopener noreferrer" class="logo-wrapper">
-            <logo :fill="currentTheme.textColor" class="logo"></logo>
+          <a
+            title="Funkwhale"
+            href="https://funkwhale.audio"
+            target="_blank"
+            rel="noopener noreferrer"
+            class="logo-wrapper"
+          >
+            <logo
+              :fill="currentTheme.textColor"
+              class="logo"
+            />
           </a>
         </section>
       </div>
     </article>
-    <div v-if="tracks.length > 1" class="queue-wrapper" id="queue">
+    <div
+      v-if="tracks.length > 1"
+      id="queue"
+      class="queue-wrapper"
+    >
       <table class="queue">
         <tbody>
           <tr
-            :id="'queue-item-' + index"
-            role="button"
+            v-for="(track, index) in tracks"
             v-if="track.sources.length > 0"
+            :id="'queue-item-' + index"
             :key="index"
+            role="button"
             :class="[{active: index === currentIndex}]"
             @click="play(index)"
             @keyup.enter="play(index)"
-            v-for="(track, index) in tracks">
-            <td class="position-cell" width="40">
+          >
+            <td
+              class="position-cell"
+              width="40"
+            >
               <span class="position">
                 {{ index + 1 }}
               </span>
             </td>
-            <td class="title" :title="track.title" ><div colspan="2" class="ellipsis">{{ track.title }}</div></td>
-            <td class="artist" :title="track.artist.name" ><div class="ellipsis">{{ track.artist.name }}</div></td>
+            <td
+              class="title"
+              :title="track.title"
+            >
+              <div
+                colspan="2"
+                class="ellipsis"
+              >
+                {{ track.title }}
+              </div>
+            </td>
+            <td
+              class="artist"
+              :title="track.artist.name"
+            >
+              <div class="ellipsis">
+                {{ track.artist.name }}
+              </div>
+            </td>
             <td class="album">
-              <div class="ellipsis" v-if="track.album" :title="track.album.title">{{ track.album.title }}</div>
+              <div
+                v-if="track.album"
+                class="ellipsis"
+                :title="track.album.title"
+              >
+                {{ track.album.title }}
+              </div>
+            </td>
+            <td width="50">
+              {{ time.durationFormatted(track.sources[0].duration) }}
             </td>
-            <td width="50">{{ time.durationFormatted(track.sources[0].duration) }}</td>
           </tr>
         </tbody>
       </table>
@@ -116,26 +240,24 @@
 
 <script>
 import axios from 'axios'
-import Logo from "@/components/Logo"
+import Logo from '@/components/Logo'
 import url from '@/utils/url'
 import time from '@/utils/time'
 
 function getURLParams () {
-  var urlParams
-  var match,
-      pl     = /\+/g,  // Regex for replacing addition symbol with a space
-      search = /([^&=]+)=?([^&]*)/g,
-      decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
-      query  = window.location.search.substring(1);
+  let match
+  const pl = /\+/g // Regex for replacing addition symbol with a space
+  const urlParams = {}
+  const search = /([^&=]+)=?([^&]*)/g
+  const decode = function (s) { return decodeURIComponent(s.replace(pl, ' ')) }
+  const query = window.location.search.substring(1)
 
-  urlParams = {};
-  while (match = search.exec(query))
-      urlParams[decode(match[1])] = decode(match[2]);
+  while (match === search.exec(query)) { urlParams[decode(match[1])] = decode(match[2]) }
   return urlParams
 }
 export default {
-  name: 'app',
-  components: {Logo},
+  name: 'App',
+  components: { Logo },
   data () {
     return {
       time,
@@ -152,38 +274,11 @@ export default {
       currentIndex: -1,
       themes: {
         dark: {
-          textColor: 'white',
+          textColor: 'white'
         }
       }
     }
   },
-  created () {
-    let params = getURLParams()
-    this.baseUrl = params.b || ''
-    this.type = params.type
-    if (this.supportedTypes.indexOf(this.type) === -1) {
-      this.error = 'invalid_type'
-    }
-    this.id = params.id
-    if (!this.id) {
-      this.error = 'invalid_id'
-    }
-    if (this.error) {
-      this.isLoading = false
-      return
-    }
-    if (!!params.instance) {
-      this.baseUrl = params.instance
-    }
-
-    this.autoplay = params.autoplay != undefined || params.auto_play != undefined
-    this.fetch(this.type, this.id)
-  },
-  mounted () {
-    var parser = document.createElement('a')
-    parser.href = this.baseUrl
-    this.url = parser
-  },
   computed: {
     currentTrack () {
       if (this.tracks.length === 0) {
@@ -195,12 +290,12 @@ export default {
       return this.themes[this.theme]
     },
     controls () {
-      return  [
+      return [
         'play', // Play/pause playback
         'progress', // The progress bar and scrubber for playback and buffering
         'current-time', // The current time of playback
         'mute', // Toggle mute
-        'volume', // Volume control
+        'volume' // Volume control
       ]
     },
     hasPrevious () {
@@ -208,7 +303,54 @@ export default {
     },
     hasNext () {
       return this.currentIndex < this.tracks.length - 1
+    }
+  },
+  watch: {
+    currentIndex (v) {
+      // we bind player events
+      const self = this
+      this.$nextTick(() => {
+        self.bindEvents()
+        if (self.tracks.length > 0) {
+          const el = document.getElementById(`queue-item-${v}`)
+          if (!el) {
+            return
+          }
+          const topPos = el.offsetTop
+          document.getElementById('queue').scrollTop = topPos - 10
+        }
+      })
     },
+    tracks () {
+      this.currentIndex = 0
+    }
+  },
+  created () {
+    const params = getURLParams()
+    this.baseUrl = params.b || ''
+    this.type = params.type
+    if (this.supportedTypes.indexOf(this.type) === -1) {
+      this.error = 'invalid_type'
+    }
+    this.id = params.id
+    if (!this.id) {
+      this.error = 'invalid_id'
+    }
+    if (this.error) {
+      this.isLoading = false
+      return
+    }
+    if (params.instance) {
+      this.baseUrl = params.instance
+    }
+
+    this.autoplay = params.autoplay !== undefined || params.auto_play !== undefined
+    this.fetch(this.type, this.id)
+  },
+  mounted () {
+    const parser = document.createElement('a')
+    parser.href = this.baseUrl
+    this.url = parser
   },
   methods: {
     next () {
@@ -221,11 +363,11 @@ export default {
         this.play(this.currentIndex - 1)
       }
     },
-    setControlFocus(event, enable) {
+    setControlFocus (event, enable) {
       if (enable) {
-        event.target.classList.add("plyr__tab-focus");
+        event.target.classList.add('plyr__tab-focus')
       } else {
-        event.target.classList.remove("plyr__tab-focus");
+        event.target.classList.remove('plyr__tab-focus')
       }
     },
     fetch (type, id) {
@@ -233,13 +375,13 @@ export default {
         this.fetchTrack(id)
       }
       if (type === 'album') {
-        this.fetchTracks({album: id, playable: true, ordering: "disc_number,position"})
+        this.fetchTracks({ album: id, playable: true, ordering: 'disc_number,position' })
       }
       if (type === 'channel') {
-        this.fetchTracks({channel: id, playable: true, include_channels: 'true', ordering: "-creation_date"})
+        this.fetchTracks({ channel: id, playable: true, include_channels: 'true', ordering: '-creation_date' })
       }
       if (type === 'artist') {
-        this.fetchTracks({artist: id, playable: true, include_channels: 'true', ordering: "-album__release_date,disc_number,position"})
+        this.fetchTracks({ artist: id, playable: true, include_channels: 'true', ordering: '-album__release_date,disc_number,position' })
       }
       if (type === 'playlist') {
         this.fetchTracks({}, `/api/v1/playlists/${id}/tracks/`)
@@ -247,67 +389,61 @@ export default {
     },
     play (index) {
       this.currentIndex = index
-      let self = this
+      const self = this
       this.$nextTick(() => {
         self.$refs.player.player.play()
       })
     },
     fetchTrack (id) {
-      let self = this
-      let url = `${this.baseUrl}/api/v1/tracks/${id}/`
+      const self = this
+      const url = `${this.baseUrl}/api/v1/tracks/${id}/`
       axios.get(url).then(response => {
         self.tracks = self.parseTracks([response.data])
-        self.isLoading = false;
+        self.isLoading = false
       }).catch(error => {
         if (error.response) {
           if (error.response.status === 404) {
             self.error = 'server_not_found'
-          }
-          else if (error.response.status === 403) {
+          } else if (error.response.status === 403) {
             self.error = 'server_requires_auth'
-          }
-          else if (error.response.status === 500) {
+          } else if (error.response.status === 500) {
             self.error = 'server_error'
-          }
-          else {
+          } else {
             self.error = 'server_unknown_error'
           }
         } else {
           self.error = 'server_unknown_error'
         }
-        self.isLoading = false;
+        self.isLoading = false
       })
     },
     fetchTracks (filters, path) {
-      path = path || "/api/v1/tracks/"
-      filters.include_channels = "true"
-      let self = this
-      let url = `${this.baseUrl}${path}`
-      axios.get(url, {params: filters}).then(response => {
+      path = path || '/api/v1/tracks/'
+      filters.include_channels = 'true'
+      const self = this
+      const url = `${this.baseUrl}${path}`
+      axios.get(url, { params: filters }).then(response => {
         self.tracks = self.parseTracks(response.data.results)
-        self.isLoading = false;
+        self.isLoading = false
       }).catch(error => {
         if (error.response) {
           if (error.response.status === 404) {
             self.error = 'server_not_found'
-          }
-          else if (error.response.status === 403) {
+          } else if (error.response.status === 403) {
             self.error = 'server_requires_auth'
-          }
-          else if (error.response.status === 500) {
+          } else if (error.response.status === 500) {
             self.error = 'server_error'
-          }
-          else {
+          } else {
             self.error = 'server_unknown_error'
           }
         } else {
           self.error = 'server_unknown_error'
         }
-        self.isLoading = false;
+        self.isLoading = false
       })
     },
     parseTracks (tracks) {
-      let self = this
+      const self = this
       if (this.type === 'playlist') {
         tracks = tracks.map((t) => {
           return t.track
@@ -325,7 +461,7 @@ export default {
       })
     },
     bindEvents () {
-      let self = this
+      const self = this
       this.$refs.player.player.on('ended', () => {
         self.next()
       })
@@ -336,17 +472,17 @@ export default {
       }
       return path
     },
-    getCover(albumCover) {
+    getCover (albumCover) {
       if (albumCover) {
         return albumCover.urls.medium_square_crop
       }
     },
     getSources (uploads) {
-      let self = this;
-      let a = document.createElement('audio')
-      let allowed = ['probably', 'maybe']
-      let sources = uploads.filter(u => {
-        let canPlay = a.canPlayType(u.mimetype)
+      const self = this
+      const a = document.createElement('audio')
+      const allowed = ['probably', 'maybe']
+      const sources = uploads.filter(u => {
+        const canPlay = a.canPlayType(u.mimetype)
         return allowed.indexOf(canPlay) > -1
       }).map(u => {
         return {
@@ -371,26 +507,6 @@ export default {
       }
       return sources
     }
-  },
-  watch: {
-    currentIndex (v) {
-      // we bind player events
-      let self = this
-      this.$nextTick(() => {
-        self.bindEvents()
-        if (self.tracks.length > 0) {
-          let el = document.getElementById(`queue-item-${v}`);
-          if (!el) {
-            return
-          }
-          var topPos = el.offsetTop;
-          document.getElementById('queue').scrollTop = topPos-10;
-        }
-      })
-    },
-    tracks () {
-      this.currentIndex = 0
-    }
   }
 }
 </script>
diff --git a/front/src/audio/backend.js b/front/src/audio/backend.js
index c37156675..20032ac13 100644
--- a/front/src/audio/backend.js
+++ b/front/src/audio/backend.js
@@ -1,4 +1,4 @@
-var Album = {
+const Album = {
   clean (album) {
     // we manually rebind the album and artist to each child track
     album.tracks = album.tracks.map((track) => {
@@ -8,7 +8,7 @@ var Album = {
     return album
   }
 }
-var Artist = {
+const Artist = {
   clean (artist) {
     // clean data as given by the API
     artist.albums = artist.albums.map((album) => {
diff --git a/front/src/audio/volume.js b/front/src/audio/volume.js
index c184a9767..1b7e2bc62 100644
--- a/front/src/audio/volume.js
+++ b/front/src/audio/volume.js
@@ -1,25 +1,25 @@
 const DYNAMIC_RANGE = 40 // dB
 
-function toLinearVolumeScale(v) {
-    if (v <= 0.0) {
-        return 0.0
-    }
+function toLinearVolumeScale (v) {
+  if (v <= 0.0) {
+    return 0.0
+  }
 
-	// (1.0; 0.0) -> (0; -DYNAMIC_RANGE) dB
-	let dB = (v-1)*DYNAMIC_RANGE
+  // (1.0; 0.0) -> (0; -DYNAMIC_RANGE) dB
+  const dB = (v - 1) * DYNAMIC_RANGE
 
-    return Math.pow(10, dB / 20)
+  return Math.pow(10, dB / 20)
 }
 
-function toLogarithmicVolumeScale(v) {
-    if (v <= 0.0) {
-        return 0.0
-    }
+function toLogarithmicVolumeScale (v) {
+  if (v <= 0.0) {
+    return 0.0
+  }
 
-	let dB = 20 * Math.log10(v)
+  const dB = 20 * Math.log10(v)
 
-	// (0; -DYNAMIC_RANGE) [dB] -> (1.0; 0.0)
-    return 1 - (dB / -DYNAMIC_RANGE)
+  // (0; -DYNAMIC_RANGE) [dB] -> (1.0; 0.0)
+  return 1 - (dB / -DYNAMIC_RANGE)
 }
 
 exports.toLinearVolumeScale = toLinearVolumeScale
diff --git a/front/src/components/Footer.vue b/front/src/components/Footer.vue
index 061031870..f510e56ab 100644
--- a/front/src/components/Footer.vue
+++ b/front/src/components/Footer.vue
@@ -1,77 +1,208 @@
 <template>
-  <footer id="footer" role="contentinfo" class="ui vertical footer segment" aria-labelledby="footer-label">
-    <h1 id="footer-label" class="visually-hidden">
-      <translate translate-context="*/*/*">Application footer</translate>
+  <footer
+    id="footer"
+    role="contentinfo"
+    class="ui vertical footer segment"
+    aria-labelledby="footer-label"
+  >
+    <h1
+      id="footer-label"
+      class="visually-hidden"
+    >
+      <translate translate-context="*/*/*">
+        Application footer
+      </translate>
     </h1>
     <div class="ui container">
       <div class="ui stackable equal height stackable grid">
         <section class="four wide column">
-          <h4 v-if="podName" class="ui header ellipsis">
-            <span v-translate="{instanceName: podName}" translate-context="Footer/About/Title">About %{instanceName}</span>
+          <h4
+            v-if="podName"
+            class="ui header ellipsis"
+          >
+            <span
+              v-translate="{instanceName: podName}"
+              translate-context="Footer/About/Title"
+            >About %{instanceName}</span>
           </h4>
-          <h4 v-else class="ui header ellipsis">
-            <span v-translate="{instanceUrl: instanceHostname}" translate-context="Footer/About/Title">About %{instanceUrl}</span>
+          <h4
+            v-else
+            class="ui header ellipsis"
+          >
+            <span
+              v-translate="{instanceUrl: instanceHostname}"
+              translate-context="Footer/About/Title"
+            >About %{instanceUrl}</span>
           </h4>
           <div class="ui list">
-            <router-link v-if="this.$route.path != '/about'" class="link item" to="/about">
-              <translate translate-context="Footer/About/List item.Link">About</translate>
+            <router-link
+              v-if="$route.path != '/about'"
+              class="link item"
+              to="/about"
+            >
+              <translate translate-context="Footer/About/List item.Link">
+                About
+              </translate>
             </router-link>
-            <router-link v-else-if="this.$route.path == '/about' && $store.state.auth.authenticated" class="link item" to="/library">
-              <translate translate-context="Footer/*/List item.Link">Go to Library</translate>
+            <router-link
+              v-else-if="$route.path == '/about' && $store.state.auth.authenticated"
+              class="link item"
+              to="/library"
+            >
+              <translate translate-context="Footer/*/List item.Link">
+                Go to Library
+              </translate>
             </router-link>
-            <router-link v-else class="link item" to="/">
-              <translate translate-context="Footer/*/List item.Link">Home Page</translate>
+            <router-link
+              v-else
+              class="link item"
+              to="/"
+            >
+              <translate translate-context="Footer/*/List item.Link">
+                Home Page
+              </translate>
             </router-link>
-              <a v-if="version" class="link item" href="https://docs.funkwhale.audio/changelog.html" target="_blank">
-                <translate translate-context="Footer/*/List item" :translate-params="{version: version}" >Version %{version}</translate>
-              </a>
-            <a role="button" href="" class="link item" @click.prevent="$emit('show:set-instance-modal')" >
+            <a
+              v-if="version"
+              class="link item"
+              href="https://docs.funkwhale.audio/changelog.html"
+              target="_blank"
+            >
+              <translate
+                translate-context="Footer/*/List item"
+                :translate-params="{version: version}"
+              >Version %{version}</translate>
+            </a>
+            <a
+              role="button"
+              href=""
+              class="link item"
+              @click.prevent="$emit('show:set-instance-modal')"
+            >
               <translate translate-context="Footer/*/List item.Link">Use another instance</translate>
             </a>
           </div>
           <div class="ui form">
             <div class="ui field">
               <label for="language-select"><translate translate-context="Footer/Settings/Dropdown.Label/Short, Verb">Change language</translate></label>
-              <select id="language-select" class="ui dropdown" :value="$language.current" @change="$store.dispatch('ui/currentLanguage', $event.target.value)">
-                <option v-for="(language, key) in $language.available" :key="key" :value="key">{{ language }}</option>
+              <select
+                id="language-select"
+                class="ui dropdown"
+                :value="$language.current"
+                @change="$store.dispatch('ui/currentLanguage', $event.target.value)"
+              >
+                <option
+                  v-for="(language, key) in $language.available"
+                  :key="key"
+                  :value="key"
+                >
+                  {{ language }}
+                </option>
               </select>
             </div>
           </div>
         </section>
         <section class="four wide column">
-          <h4 v-translate class="ui header" translate-context="Footer/*/Title">Using Funkwhale</h4>
+          <h4
+            v-translate
+            class="ui header"
+            translate-context="Footer/*/Title"
+          >
+            Using Funkwhale
+          </h4>
           <div class="ui list">
-            <a href="https://docs.funkwhale.audio" class="link item" target="_blank"><translate translate-context="Footer/*/List item.Link/Short, Noun">Documentation</translate></a>
-            <a href="https://funkwhale.audio/apps" class="link item" target="_blank"><translate translate-context="Footer/*/List item.Link">Mobile and desktop apps</translate></a>
-            <a hrelf="" class="link item" @click.prevent="$emit('show:shortcuts-modal')"><translate translate-context="*/*/*/Noun">Keyboard shortcuts</translate></a>
+            <a
+              href="https://docs.funkwhale.audio"
+              class="link item"
+              target="_blank"
+            ><translate translate-context="Footer/*/List item.Link/Short, Noun">Documentation</translate></a>
+            <a
+              href="https://funkwhale.audio/apps"
+              class="link item"
+              target="_blank"
+            ><translate translate-context="Footer/*/List item.Link">Mobile and desktop apps</translate></a>
+            <a
+              hrelf=""
+              class="link item"
+              @click.prevent="$emit('show:shortcuts-modal')"
+            ><translate translate-context="*/*/*/Noun">Keyboard shortcuts</translate></a>
           </div>
           <div class="ui form">
             <div class="ui field">
               <label for="theme-select"><translate translate-context="Footer/Settings/Dropdown.Label/Short, Verb">Change theme</translate></label>
-              <select id="theme-select" class="ui dropdown" :value="$store.state.ui.theme" @change="$store.dispatch('ui/theme', $event.target.value)">
-                <option v-for="theme in themes" :key="theme.key" :value="theme.key">{{ theme.name }}</option>
+              <select
+                id="theme-select"
+                class="ui dropdown"
+                :value="$store.state.ui.theme"
+                @change="$store.dispatch('ui/theme', $event.target.value)"
+              >
+                <option
+                  v-for="theme in themes"
+                  :key="theme.key"
+                  :value="theme.key"
+                >
+                  {{ theme.name }}
+                </option>
               </select>
             </div>
           </div>
         </section>
         <section class="four wide column">
-          <h4 v-translate translate-context="Footer/*/Link" class="ui header">Getting help</h4>
+          <h4
+            v-translate
+            translate-context="Footer/*/Link"
+            class="ui header"
+          >
+            Getting help
+          </h4>
           <div class="ui list">
-            <a href="https://forum.funkwhale.audio/" class="link item" target="_blank"><translate translate-context="Footer/*/Listitem.Link">Support forum</translate></a>
-            <a href="https://matrix.to/#/#funkwhale-troubleshooting:matrix.org" class="link item" target="_blank"><translate translate-context="Footer/*/List item.Link">Chat room</translate></a>
-            <a href="https://dev.funkwhale.audio/funkwhale/funkwhale/issues" class="link item" target="_blank"><translate translate-context="Footer/*/List item.Link">Issue tracker</translate></a>
+            <a
+              href="https://forum.funkwhale.audio/"
+              class="link item"
+              target="_blank"
+            ><translate translate-context="Footer/*/Listitem.Link">Support forum</translate></a>
+            <a
+              href="https://matrix.to/#/#funkwhale-troubleshooting:matrix.org"
+              class="link item"
+              target="_blank"
+            ><translate translate-context="Footer/*/List item.Link">Chat room</translate></a>
+            <a
+              href="https://dev.funkwhale.audio/funkwhale/funkwhale/issues"
+              class="link item"
+              target="_blank"
+            ><translate translate-context="Footer/*/List item.Link">Issue tracker</translate></a>
           </div>
         </section>
         <section class="four wide column">
-          <h4 v-translate class="ui header" translate-context="Footer/*/Title/Short">About Funkwhale</h4>
+          <h4
+            v-translate
+            class="ui header"
+            translate-context="Footer/*/Title/Short"
+          >
+            About Funkwhale
+          </h4>
           <div class="ui list">
-            <a href="https://funkwhale.audio" class="link item" target="_blank"><translate translate-context="Footer/*/List item.Link">Official website</translate></a>
-            <a href="https://contribute.funkwhale.audio" class="link item" target="_blank"><translate translate-context="Footer/*/List item.Link">Contribute</translate></a>
-            <a href="https://dev.funkwhale.audio/funkwhale/funkwhale" class="link item" target="_blank"><translate translate-context="Footer/*/List item.Link">Source code</translate></a>
+            <a
+              href="https://funkwhale.audio"
+              class="link item"
+              target="_blank"
+            ><translate translate-context="Footer/*/List item.Link">Official website</translate></a>
+            <a
+              href="https://contribute.funkwhale.audio"
+              class="link item"
+              target="_blank"
+            ><translate translate-context="Footer/*/List item.Link">Contribute</translate></a>
+            <a
+              href="https://dev.funkwhale.audio/funkwhale/funkwhale"
+              class="link item"
+              target="_blank"
+            ><translate translate-context="Footer/*/List item.Link">Source code</translate></a>
           </div>
-          <div class="ui hidden divider"></div>
+          <div class="ui hidden divider" />
           <p>
-            <translate translate-context="Footer/*/List item.Link">The Funkwhale logo was kindly designed and provided by Francis Gading.</translate>
+            <translate translate-context="Footer/*/List item.Link">
+              The Funkwhale logo was kindly designed and provided by Francis Gading.
+            </translate>
           </p>
         </section>
       </div>
@@ -80,24 +211,22 @@
 </template>
 
 <script>
-import Vue from "vue"
-import { mapState } from "vuex"
-import axios from 'axios'
+import { mapState } from 'vuex'
 import _ from '@/lodash'
 
 export default {
-  props: ["version"],
+  props: { version: { type: String, required: true } },
   computed: {
     ...mapState({
       messages: state => state.ui.messages,
-      nodeinfo: state => state.instance.nodeinfo,
+      nodeinfo: state => state.instance.nodeinfo
     }),
-    podName() {
+    podName () {
       return _.get(this.nodeinfo, 'metadata.nodeName')
     },
-    instanceHostname() {
-      let url = this.$store.state.instance.instanceUrl
-      let parser = document.createElement("a")
+    instanceHostname () {
+      const url = this.$store.state.instance.instanceUrl
+      const parser = document.createElement('a')
       parser.href = url
       return parser.hostname
     },
diff --git a/front/src/components/Home.vue b/front/src/components/Home.vue
index 92615c985..9b4c8194b 100644
--- a/front/src/components/Home.vue
+++ b/front/src/components/Home.vue
@@ -1,15 +1,25 @@
 <template>
-  <main class="main pusher page-home" v-title="labels.title">
-    <section :class="['ui', 'head', {'with-background': banner}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle">
+  <main
+    v-title="labels.title"
+    class="main pusher page-home"
+  >
+    <section
+      :class="['ui', 'head', {'with-background': banner}, 'vertical', 'center', 'aligned', 'stripe', 'segment']"
+      :style="headerStyle"
+    >
       <div class="segment-content">
         <h1 class="ui center aligned large header">
           <span
             v-translate="{podName: podName}"
             translate-context="Content/Home/Header"
-            :translate-params="{podName: podName}">
+            :translate-params="{podName: podName}"
+          >
             Welcome to %{ podName }!
           </span>
-          <div v-if="shortDescription" class="sub header">
+          <div
+            v-if="shortDescription"
+            class="sub header"
+          >
             {{ shortDescription }}
           </div>
         </h1>
@@ -19,31 +29,61 @@
       <div class="ui stackable grid">
         <div class="ten wide column">
           <h2 class="header">
-            <translate translate-context="Content/Home/Header">About this Funkwhale pod</translate>
+            <translate translate-context="Content/Home/Header">
+              About this Funkwhale pod
+            </translate>
           </h2>
-          <div class="ui raised segment" id="pod">
+          <div
+            id="pod"
+            class="ui raised segment"
+          >
             <div class="ui stackable grid">
               <div class="eight wide column">
                 <p v-if="!truncatedDescription">
-                  <translate translate-context="Content/Home/Paragraph">No description available.</translate>
+                  <translate translate-context="Content/Home/Paragraph">
+                    No description available.
+                  </translate>
                 </p>
                 <template v-if="truncatedDescription || rules">
-                  <div v-if="truncatedDescription" v-html="truncatedDescription"></div>
-                  <div v-if="truncatedDescription" class="ui hidden divider"></div>
+                  <div
+                    v-if="truncatedDescription"
+                    v-html="truncatedDescription"
+                  />
+                  <div
+                    v-if="truncatedDescription"
+                    class="ui hidden divider"
+                  />
                   <div class="ui relaxed list">
-                    <div class="item" v-if="truncatedDescription">
-                      <i class="arrow right icon"></i>
+                    <div
+                      v-if="truncatedDescription"
+                      class="item"
+                    >
+                      <i class="arrow right icon" />
                       <div class="content">
-                        <router-link class="ui link" :to="{name: 'about'}">
-                          <translate translate-context="Content/Home/Link">Learn more</translate>
+                        <router-link
+                          class="ui link"
+                          :to="{name: 'about'}"
+                        >
+                          <translate translate-context="Content/Home/Link">
+                            Learn more
+                          </translate>
                         </router-link>
                       </div>
                     </div>
-                    <div class="item" v-if="rules">
-                      <i class="book open icon"></i>
+                    <div
+                      v-if="rules"
+                      class="item"
+                    >
+                      <i class="book open icon" />
                       <div class="content">
-                        <router-link class="ui link" v-if="rules" :to="{name: 'about', hash: '#rules'}">
-                          <translate translate-context="Content/Home/Link">Server rules</translate>
+                        <router-link
+                          v-if="rules"
+                          class="ui link"
+                          :to="{name: 'about', hash: '#rules'}"
+                        >
+                          <translate translate-context="Content/Home/Link">
+                            Server rules
+                          </translate>
                         </router-link>
                       </div>
                     </div>
@@ -53,71 +93,130 @@
               <div class="eight wide column">
                 <template v-if="stats">
                   <h3 class="sub header">
-                    <translate translate-context="Content/Home/Header">Statistics</translate>
+                    <translate translate-context="Content/Home/Header">
+                      Statistics
+                    </translate>
                   </h3>
                   <p>
-                    <i class="user icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.users.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.users" translate-plural="%{ count } active users">%{ count } active user</translate>
+                    <i class="user icon" /><translate
+                      translate-context="Content/Home/Stat"
+                      :translate-params="{count: stats.users.toLocaleString($store.state.ui.momentLocale) }"
+                      :translate-n="stats.users"
+                      translate-plural="%{ count } active users"
+                    >
+                      %{ count } active user
+                    </translate>
                   </p>
                   <p>
-                    <i class="music icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: parseInt(stats.hours).toLocaleString($store.state.ui.momentLocale)}" :translate-n="parseInt(stats.hours)" translate-plural="%{ count } hours of music">%{ count } hour of music</translate>
+                    <i class="music icon" /><translate
+                      translate-context="Content/Home/Stat"
+                      :translate-params="{count: parseInt(stats.hours).toLocaleString($store.state.ui.momentLocale)}"
+                      :translate-n="parseInt(stats.hours)"
+                      translate-plural="%{ count } hours of music"
+                    >
+                      %{ count } hour of music
+                    </translate>
                   </p>
-
                 </template>
                 <template v-if="contactEmail">
                   <h3 class="sub header">
-                    <translate translate-context="Content/Home/Header/Name">Contact</translate>
+                    <translate translate-context="Content/Home/Header/Name">
+                      Contact
+                    </translate>
                   </h3>
-                  <i class="at icon"></i>
+                  <i class="at icon" />
                   <a :href="`mailto:${contactEmail}`">{{ contactEmail }}</a>
                 </template>
-
               </div>
             </div>
           </div>
         </div>
 
         <div class="six wide column">
-          <img class="ui image" src="../assets/network.png" alt=""/>
+          <img
+            class="ui image"
+            src="../assets/network.png"
+            alt=""
+          >
         </div>
       </div>
-      <div class="ui hidden divider"></div>
-      <div class="ui hidden divider"></div>
+      <div class="ui hidden divider" />
+      <div class="ui hidden divider" />
       <div class="ui stackable grid">
         <div class="four wide column">
           <h3 class="header">
-            <translate translate-context="Footer/*/Title/Short">About Funkwhale</translate>
+            <translate translate-context="Footer/*/Title/Short">
+              About Funkwhale
+            </translate>
           </h3>
-          <p v-translate translate-context="Content/Home/Paragraph">This pod runs Funkwhale, a community-driven project that lets you listen and share music and audio within a decentralized, open network.</p>
-          <p v-translate translate-context="Content/Home/Paragraph">Funkwhale is free and developed by a friendly community of volunteers.</p>
-          <a target="_blank" rel="noopener" href="https://funkwhale.audio">
-            <i class="external alternate icon"></i>
+          <p
+            v-translate
+            translate-context="Content/Home/Paragraph"
+          >
+            This pod runs Funkwhale, a community-driven project that lets you listen and share music and audio within a decentralized, open network.
+          </p>
+          <p
+            v-translate
+            translate-context="Content/Home/Paragraph"
+          >
+            Funkwhale is free and developed by a friendly community of volunteers.
+          </p>
+          <a
+            target="_blank"
+            rel="noopener"
+            href="https://funkwhale.audio"
+          >
+            <i class="external alternate icon" />
             <translate translate-context="Content/Home/Link">Visit funkwhale.audio</translate>
           </a>
         </div>
         <div class="four wide column">
           <h3 class="header">
-            <translate translate-context="Head/Login/Title">Log In</translate>
+            <translate translate-context="Head/Login/Title">
+              Log In
+            </translate>
           </h3>
-          <login-form button-classes="success" :show-signup="false"></login-form>
-          <div class="ui hidden clearing divider"></div>
+          <login-form
+            button-classes="success"
+            :show-signup="false"
+          />
+          <div class="ui hidden clearing divider" />
         </div>
         <div class="four wide column">
           <h3 class="header">
-            <translate translate-context="*/Signup/Title">Sign up</translate>
+            <translate translate-context="*/Signup/Title">
+              Sign up
+            </translate>
           </h3>
           <template v-if="openRegistrations">
             <p>
-              <translate translate-context="Content/Home/Paragraph">Sign up now to keep track of your favorites, create playlists, discover new content and much more!</translate>
+              <translate translate-context="Content/Home/Paragraph">
+                Sign up now to keep track of your favorites, create playlists, discover new content and much more!
+              </translate>
             </p>
             <p v-if="defaultUploadQuota">
-              <translate translate-context="Content/Home/Paragraph" :translate-params="{quota: humanSize(defaultUploadQuota * 1000 * 1000)}">Users on this pod also get %{ quota } of free storage to upload their own content!</translate>
+              <translate
+                translate-context="Content/Home/Paragraph"
+                :translate-params="{quota: humanSize(defaultUploadQuota * 1000 * 1000)}"
+              >
+                Users on this pod also get %{ quota } of free storage to upload their own content!
+              </translate>
             </p>
-            <signup-form button-classes="success" :show-login="false"></signup-form>
+            <signup-form
+              button-classes="success"
+              :show-login="false"
+            />
           </template>
           <div v-else>
-            <p translate-context="Content/Home/Paragraph">Registrations are closed on this pod. You can signup on another pod using the link below.</p>
-            <a target="_blank" rel="noopener" href="https://funkwhale.audio/#get-started">
-              <i class="external alternate icon"></i>
+            <p translate-context="Content/Home/Paragraph">
+              Registrations are closed on this pod. You can signup on another pod using the link below.
+            </p>
+            <a
+              target="_blank"
+              rel="noopener"
+              href="https://funkwhale.audio/#get-started"
+            >
+              <i class="external alternate icon" />
               <translate translate-context="Content/Home/Link">Find another pod</translate>
             </a>
           </div>
@@ -125,39 +224,63 @@
 
         <div class="four wide column">
           <h3 class="header">
-            <translate translate-context="Content/Home/Header">Useful links</translate>
+            <translate translate-context="Content/Home/Header">
+              Useful links
+            </translate>
           </h3>
           <div class="ui relaxed list">
             <div class="item">
-              <i class="headphones icon"></i>
+              <i class="headphones icon" />
               <div class="content">
-                <router-link v-if="anonymousCanListen" class="header" to="/library">
-                  <translate translate-context="Content/Home/Link">Browse public content</translate>
+                <router-link
+                  v-if="anonymousCanListen"
+                  class="header"
+                  to="/library"
+                >
+                  <translate translate-context="Content/Home/Link">
+                    Browse public content
+                  </translate>
                 </router-link>
                 <div class="description">
-                  <translate translate-context="Content/Home/Link">Listen to public albums and playlists shared on this pod</translate>
+                  <translate translate-context="Content/Home/Link">
+                    Listen to public albums and playlists shared on this pod
+                  </translate>
                 </div>
               </div>
             </div>
             <div class="item">
-              <i class="mobile alternate icon"></i>
+              <i class="mobile alternate icon" />
               <div class="content">
-                <a class="header" href="https://funkwhale.audio/apps" target="_blank" rel="noopener">
+                <a
+                  class="header"
+                  href="https://funkwhale.audio/apps"
+                  target="_blank"
+                  rel="noopener"
+                >
                   <translate translate-context="Content/Home/Link">Mobile apps</translate>
                 </a>
                 <div class="description">
-                  <translate translate-context="Content/Home/Link">Use Funkwhale on other devices with our apps</translate>
+                  <translate translate-context="Content/Home/Link">
+                    Use Funkwhale on other devices with our apps
+                  </translate>
                 </div>
               </div>
             </div>
             <div class="item">
-              <i class="book icon"></i>
+              <i class="book icon" />
               <div class="content">
-                <a class="header" href="https://docs.funkwhale.audio/users/index.html" target="_blank" rel="noopener">
+                <a
+                  class="header"
+                  href="https://docs.funkwhale.audio/users/index.html"
+                  target="_blank"
+                  rel="noopener"
+                >
                   <translate translate-context="Content/Home/Link">User guides</translate>
                 </a>
                 <div class="description">
-                  <translate translate-context="Content/Home/Link">Discover everything you need to know about Funkwhale and its features</translate>
+                  <translate translate-context="Content/Home/Link">
+                    Discover everything you need to know about Funkwhale and its features
+                  </translate>
                 </div>
               </div>
             </div>
@@ -165,20 +288,37 @@
         </div>
       </div>
     </section>
-    <section v-if="anonymousCanListen" class="ui vertical stripe segment">
-      <album-widget :filters="{playable: true, ordering: '-creation_date'}" :limit="10">
-        <template slot="title"><translate translate-context="Content/Home/Title">Recently added albums</translate></template>
+    <section
+      v-if="anonymousCanListen"
+      class="ui vertical stripe segment"
+    >
+      <album-widget
+        :filters="{playable: true, ordering: '-creation_date'}"
+        :limit="10"
+      >
+        <template slot="title">
+          <translate translate-context="Content/Home/Title">
+            Recently added albums
+          </translate>
+        </template>
         <router-link to="/library">
-          <translate translate-context="Content/Home/Link">View more…</translate>
-          <div class="ui hidden divider"></div>
+          <translate translate-context="Content/Home/Link">
+            View more…
+          </translate>
+          <div class="ui hidden divider" />
         </router-link>
       </album-widget>
-      <div class="ui hidden section divider"></div>
-      <h3 class="ui header" >
-        <translate translate-context="*/*/*">New channels</translate>
+      <div class="ui hidden section divider" />
+      <h3 class="ui header">
+        <translate translate-context="*/*/*">
+          New channels
+        </translate>
       </h3>
-      <channels-widget :show-modification-date="true" :limit="10" :filters="{ordering: '-creation_date', external: 'false'}"></channels-widget>
-
+      <channels-widget
+        :show-modification-date="true"
+        :limit="10"
+        :filters="{ordering: '-creation_date', external: 'false'}"
+      />
     </section>
   </main>
 </template>
@@ -186,20 +326,20 @@
 <script>
 import $ from 'jquery'
 import _ from '@/lodash'
-import {mapState} from 'vuex'
+import { mapState } from 'vuex'
 import showdown from 'showdown'
-import AlbumWidget from "@/components/audio/album/Widget"
-import ChannelsWidget from "@/components/audio/ChannelsWidget"
-import LoginForm from "@/components/auth/LoginForm"
-import SignupForm from "@/components/auth/SignupForm"
-import {humanSize } from '@/filters'
+import AlbumWidget from '@/components/audio/album/Widget'
+import ChannelsWidget from '@/components/audio/ChannelsWidget'
+import LoginForm from '@/components/auth/LoginForm'
+import SignupForm from '@/components/auth/SignupForm'
+import { humanSize } from '@/filters'
 
 export default {
   components: {
     AlbumWidget,
     ChannelsWidget,
     LoginForm,
-    SignupForm,
+    SignupForm
   },
   data () {
     return {
@@ -210,15 +350,15 @@ export default {
   },
   computed: {
     ...mapState({
-      nodeinfo: state => state.instance.nodeinfo,
+      nodeinfo: state => state.instance.nodeinfo
     }),
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext('Head/Home/Title', "Home")
+        title: this.$pgettext('Head/Home/Title', 'Home')
       }
     },
-    podName() {
-      return _.get(this.nodeinfo, 'metadata.nodeName') || "Funkwhale"
+    podName () {
+      return _.get(this.nodeinfo, 'metadata.nodeName') || 'Funkwhale'
     },
     banner () {
       return _.get(this.nodeinfo, 'metadata.banner')
@@ -236,12 +376,12 @@ export default {
       if (!this.longDescription) {
         return
       }
-      let doc = this.markdown.makeHtml(this.longDescription)
-      let nodes = $.parseHTML(doc)
-      let excerptParts = []
+      const doc = this.markdown.makeHtml(this.longDescription)
+      const nodes = $.parseHTML(doc)
+      const excerptParts = []
       let handled = 0
       nodes.forEach((n) => {
-        let content = n.innerHTML || n.nodeValue
+        const content = n.innerHTML || n.nodeValue
         if (handled < this.excerptLength && content.trim()) {
           excerptParts.push(n)
           handled += 1
@@ -250,9 +390,9 @@ export default {
       return excerptParts.map((p) => { return p.outerHTML }).join('')
     },
     stats () {
-      let data = {
+      const data = {
         users: _.get(this.nodeinfo, 'usage.users.activeMonth', null),
-        hours: _.get(this.nodeinfo, 'metadata.library.music.hours', null),
+        hours: _.get(this.nodeinfo, 'metadata.library.music.hours', null)
       }
       if (data.users === null || data.artists === null) {
         return
@@ -271,16 +411,16 @@ export default {
     openRegistrations () {
       return _.get(this.nodeinfo, 'openRegistrations')
     },
-    headerStyle() {
+    headerStyle () {
       if (!this.banner) {
-        return ""
+        return ''
       }
       return (
-        "background-image: url(" +
-        this.$store.getters["instance/absoluteUrl"](this.banner) +
-        ")"
+        'background-image: url(' +
+        this.$store.getters['instance/absoluteUrl'](this.banner) +
+        ')'
       )
-    },
+    }
   },
   watch: {
     '$store.state.auth.authenticated': {
diff --git a/front/src/components/Logo.vue b/front/src/components/Logo.vue
index 9785437c9..073edb958 100644
--- a/front/src/components/Logo.vue
+++ b/front/src/components/Logo.vue
@@ -1,31 +1,50 @@
 <template>
-  <svg version="1.1" id="layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-     viewBox="0 0 141.7 141.7" enable-background="new 0 0 141.7 141.7" xml:space="preserve">
-  <g>
+  <svg
+    id="layer_1"
+    version="1.1"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    x="0px"
+    y="0px"
+    viewBox="0 0 141.7 141.7"
+    enable-background="new 0 0 141.7 141.7"
+    xml:space="preserve"
+  >
     <g>
-      <path :fill="fill" d="M70.9,86.1c11.7,0,21.2-9.5,21.2-21.2c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1c0,6-4.9,11-11,11
-        c-6,0-11-4.9-11-11c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1C49.7,76.6,59.2,86.1,70.9,86.1z"/>
-      <path :fill="fill" d="M70.9,106.1c22.7,0,41.2-18.5,41.2-41.2c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1
+      <g>
+        <path
+          :fill="fill"
+          d="M70.9,86.1c11.7,0,21.2-9.5,21.2-21.2c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1c0,6-4.9,11-11,11
+        c-6,0-11-4.9-11-11c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1C49.7,76.6,59.2,86.1,70.9,86.1z"
+        />
+        <path
+          :fill="fill"
+          d="M70.9,106.1c22.7,0,41.2-18.5,41.2-41.2c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1
         c0,17.1-13.9,31-31,31c-17.1,0-31-13.9-31-31c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1C29.6,87.6,48.1,106.1,70.9,106.1z"
         />
-      <path :fill="fill" d="M131.1,63.8h-8c-0.6,0-1.1,0.5-1.1,1.1C122,93.1,99,116,70.9,116c-28.2,0-51.1-22.9-51.1-51.1
+        <path
+          :fill="fill"
+          d="M131.1,63.8h-8c-0.6,0-1.1,0.5-1.1,1.1C122,93.1,99,116,70.9,116c-28.2,0-51.1-22.9-51.1-51.1
         c0-0.6-0.5-1.1-1.1-1.1h-8c-0.6,0-1.1,0.5-1.1,1.1c0,33.8,27.5,61.3,61.3,61.3c33.8,0,61.3-27.5,61.3-61.3
-        C132.2,64.3,131.7,63.8,131.1,63.8z"/>
-    </g>
-    <path :fill="fill" d="M43.3,37.3c4.1,2.1,8.5,2.5,12.5,4.8c2.6,1.5,4.2,3.2,5.8,5.7c2.5,3.8,2.4,8.5,2.4,8.5l0.3,5.2
+        C132.2,64.3,131.7,63.8,131.1,63.8z"
+        />
+      </g>
+      <path
+        :fill="fill"
+        d="M43.3,37.3c4.1,2.1,8.5,2.5,12.5,4.8c2.6,1.5,4.2,3.2,5.8,5.7c2.5,3.8,2.4,8.5,2.4,8.5l0.3,5.2
       c0,0,2,5.2,6.4,5.2c4.7,0,6.4-5.2,6.4-5.2l0.3-5.2c0,0-0.1-4.7,2.4-8.5c1.6-2.5,3.2-4.3,5.8-5.7c4-2.3,8.4-2.7,12.5-4.8
       c4.1-2.1,8.1-4.8,10.8-8.6c2.7-3.8,4-8.8,2.5-13.2c-7.8-0.4-16.8,0.5-23.7,4.2c-9.6,5.1-15.4,3.3-17.1,10.9h-0.1
-      c-1.7-7.7-7.5-5.8-17.1-10.9c-6.9-3.7-15.9-4.6-23.7-4.2c-1.5,4.4-0.2,9.4,2.5,13.2C35.2,32.5,39.2,35.2,43.3,37.3z"/>
-  </g>
+      c-1.7-7.7-7.5-5.8-17.1-10.9c-6.9-3.7-15.9-4.6-23.7-4.2c-1.5,4.4-0.2,9.4,2.5,13.2C35.2,32.5,39.2,35.2,43.3,37.3z"
+      />
+    </g>
   </svg>
-
 </template>
 
 <script>
 
 export default {
   props: {
-    fill: {type: String, default: '#222222'}
+    fill: { type: String, default: '#222222' }
   }
 }
 </script>
diff --git a/front/src/components/LogoText.vue b/front/src/components/LogoText.vue
index f60b3f326..6e9cc3fc1 100644
--- a/front/src/components/LogoText.vue
+++ b/front/src/components/LogoText.vue
@@ -1,5 +1,8 @@
 <template>
-  <svg viewBox="0 0 271.66678 53.49133" version="1.1">
+  <svg
+    viewBox="0 0 271.66678 53.49133"
+    version="1.1"
+  >
     <g transform="translate(34.65295 -109.48195)">
       <g>
         <g transform="matrix(.3191 0 0 .3191 -45.91741 93.47184)">
@@ -14,7 +17,11 @@
             </g>
           </g>
         </g>
-        <g transform="translate(-.75595 -.75595)" :fill="text" stroke-width=".74383">
+        <g
+          transform="translate(-.75595 -.75595)"
+          :fill="text"
+          stroke-width=".74383"
+        >
           <path d="M32.84591 132.89252c0-6.69443 2.6034-9.29781 10.41356-9.29781 1.63641 0 3.71912.14876 4.83486.37191.59506.14876 1.11574.59506 1.11574 1.11574v2.00832c0 .59506-.4463 1.11574-1.11574 1.11574h-.66944c-.8182 0-1.48765-.29753-2.529-.29753-4.83487 0-5.80184.96698-5.80184 4.98363v.29753h6.62004c.59506 0 1.11574.4463 1.11574 1.11574v2.15709c0 .66945-.4463 1.11574-1.11574 1.11574h-6.62004v11.30614c0 .59506-.4463 1.11574-1.11574 1.11574h-4.01666c-.59506 0-1.11574-.52068-1.11574-1.11574z" />
           <path d="M57.02023 141.59528c0 3.04968 1.41327 4.31418 3.49598 4.31418 1.78518 0 3.49598-1.2645 4.83487-2.60339v-12.12435c0-.59506.52068-1.11573 1.11574-1.11573h4.09103c.59506 0 1.11574.52067 1.11574 1.11573v17.70304c0 .59506-.4463 1.11574-1.11574 1.11574h-4.09104c-.59505 0-1.11573-.52068-1.11573-1.11574v-1.19012c-1.7108 1.48765-3.57036 2.67777-6.32252 2.67777-4.83486 0-8.25646-2.529-8.25646-8.70275v-10.41355c0-.59506.4463-1.11574 1.11574-1.11574h4.09104c.59506 0 1.11574.52068 1.11574 1.11574v10.33917z" />
           <path d="M90.71552 138.47121c0-3.04968-1.41327-4.31419-3.49598-4.31419-1.78518 0-3.57036 1.26451-4.90925 2.60339v12.19874c0 .59506-.4463 1.11573-1.11573 1.11573h-4.09104c-.66945 0-1.11574-.52067-1.11574-1.11573v-17.77743c0-.59506.4463-1.11573 1.11574-1.11573h4.16542c.59506 0 1.11574.52067 1.11574 1.11573v1.19012c1.7108-1.48765 3.57036-2.67777 6.3969-2.67777 4.83486 0 8.25645 2.52901 8.25645 8.70276v10.41355c0 .59506-.4463 1.11574-1.11573 1.11574h-4.09104c-.59506 0-1.11574-.52068-1.11574-1.11574z" />
@@ -33,9 +40,9 @@
 <script>
 export default {
   props: {
-    primary: {type: String, default: '#009fe3'},
-    secondary: {type: String, default: 'var(--text-color)'},
-    text: {type: String, default: 'var(--text-color)'},
+    primary: { type: String, default: '#009fe3' },
+    secondary: { type: String, default: 'var(--text-color)' },
+    text: { type: String, default: 'var(--text-color)' }
   }
 }
 </script>
diff --git a/front/src/components/PageNotFound.vue b/front/src/components/PageNotFound.vue
index 274a5798c..57d7c3350 100644
--- a/front/src/components/PageNotFound.vue
+++ b/front/src/components/PageNotFound.vue
@@ -1,19 +1,33 @@
 <template>
-  <main class="main pusher" :v-title="labels.title">
+  <main
+    class="main pusher"
+    :v-title="labels.title"
+  >
     <section class="ui vertical stripe segment">
       <div class="ui text container">
         <h1 class="ui huge header">
-          <i class="warning icon"></i>
+          <i class="warning icon" />
           <div class="content">
-            <translate translate-context="Content/*/Title">Page not found!</translate>
+            <translate translate-context="Content/*/Title">
+              Page not found!
+            </translate>
           </div>
         </h1>
-        <p><translate translate-context="Content/*/Paragraph">Sorry, the page you asked for does not exist:</translate></p>
+        <p>
+          <translate translate-context="Content/*/Paragraph">
+            Sorry, the page you asked for does not exist:
+          </translate>
+        </p>
         <a :href="path">{{ path }}</a>
-        <div class="ui hidden divider"></div>
-        <router-link class="ui icon labeled right button" to="/">
-          <translate translate-context="Content/*/Button.Label/Verb">Go to home page</translate>
-          <i class="right arrow icon"></i>
+        <div class="ui hidden divider" />
+        <router-link
+          class="ui icon labeled right button"
+          to="/"
+        >
+          <translate translate-context="Content/*/Button.Label/Verb">
+            Go to home page
+          </translate>
+          <i class="right arrow icon" />
         </router-link>
       </div>
     </section>
@@ -22,15 +36,15 @@
 
 <script>
 export default {
-  data: function() {
+  data: function () {
     return {
       path: window.location.href
     }
   },
   computed: {
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext('Head/*/Title', "Page Not Found")
+        title: this.$pgettext('Head/*/Title', 'Page Not Found')
       }
     }
   }
diff --git a/front/src/components/Pagination.vue b/front/src/components/Pagination.vue
index d25e72b15..4f6353b71 100644
--- a/front/src/components/Pagination.vue
+++ b/front/src/components/Pagination.vue
@@ -1,57 +1,68 @@
 <template>
-  <div v-if='maxPage > 1' class="ui pagination menu component-pagination" role="navigation" :aria-label="labels.pagination">
-    <a href
+  <div
+    v-if="maxPage > 1"
+    class="ui pagination menu component-pagination"
+    role="navigation"
+    :aria-label="labels.pagination"
+  >
+    <a
+      href
       :disabled="current - 1 < 1"
       role="button"
       :aria-label="labels.previousPage"
+      :class="[{'disabled': current - 1 < 1}, 'item']"
       @click.prevent.stop="selectPage(current - 1)"
-      :class="[{'disabled': current - 1 < 1}, 'item']"><i class="angle left icon"></i></a>
+    ><i class="angle left icon" /></a>
     <template v-if="!compact">
-      <a href
+      <a
         v-for="page in pages"
         :key="page"
+        href
+        :class="[{'active': page === current}, {'disabled': page === 'skip'}, 'item']"
         @click.prevent.stop="selectPage(page)"
-        :class="[{'active': page === current}, {'disabled': page === 'skip'}, 'item']">
+      >
         <span v-if="page !== 'skip'">{{ page }}</span>
         <span v-else>…</span>
       </a>
     </template>
-    <a href
+    <a
+      href
       :disabled="current + 1 > maxPage"
       role="button"
       :aria-label="labels.nextPage"
+      :class="[{'disabled': current + 1 > maxPage}, 'item']"
       @click.prevent.stop="selectPage(current + 1)"
-      :class="[{'disabled': current + 1 > maxPage}, 'item']"><i class="angle right icon"></i></a>
+    ><i class="angle right icon" /></a>
   </div>
 </template>
 
 <script>
-import _ from "@/lodash"
+import _ from '@/lodash'
 
 export default {
   props: {
     current: { type: Number, default: 1 },
     paginateBy: { type: Number, default: 25 },
-    total: { type: Number },
+    total: { type: Number, required: true },
     compact: { type: Boolean, default: false }
   },
   computed: {
-    labels() {
+    labels () {
       return {
-        pagination: this.$pgettext('Content/*/Hidden text/Noun', "Pagination"),
-        previousPage: this.$pgettext('Content/*/Link', "Previous Page"),
-        nextPage: this.$pgettext('Content/*/Link', "Next Page")
+        pagination: this.$pgettext('Content/*/Hidden text/Noun', 'Pagination'),
+        previousPage: this.$pgettext('Content/*/Link', 'Previous Page'),
+        nextPage: this.$pgettext('Content/*/Link', 'Next Page')
       }
     },
-    pages: function() {
-      let range = 2
-      let current = this.current
-      let beginning = _.range(1, Math.min(this.maxPage, 1 + range))
-      let middle = _.range(
+    pages: function () {
+      const range = 2
+      const current = this.current
+      const beginning = _.range(1, Math.min(this.maxPage, 1 + range))
+      const middle = _.range(
         Math.max(1, current - range + 1),
         Math.min(this.maxPage, current + range)
       )
-      let end = _.range(this.maxPage, Math.max(1, this.maxPage - range))
+      const end = _.range(this.maxPage, Math.max(1, this.maxPage - range))
       let allowed = beginning.concat(middle, end)
       allowed = _.uniq(allowed)
       allowed = _.sortBy(allowed, [
@@ -59,11 +70,11 @@ export default {
           return e
         }
       ])
-      let final = []
+      const final = []
       allowed.forEach(p => {
-        let last = final.slice(-1)[0]
+        const last = final.slice(-1)[0]
         let consecutive = true
-        if (last === "skip") {
+        if (last === 'skip') {
           consecutive = false
         } else {
           if (!last) {
@@ -75,25 +86,25 @@ export default {
         if (consecutive) {
           final.push(p)
         } else {
-          if (p !== "skip") {
-            final.push("skip")
+          if (p !== 'skip') {
+            final.push('skip')
             final.push(p)
           }
         }
       })
       return final
     },
-    maxPage: function() {
+    maxPage: function () {
       return Math.ceil(this.total / this.paginateBy)
     }
   },
   methods: {
-    selectPage: function(page) {
+    selectPage: function (page) {
       if (page > this.maxPage || page < 1) {
         return
       }
       if (this.current !== page) {
-        this.$emit("page-changed", page)
+        this.$emit('page-changed', page)
       }
     }
   }
diff --git a/front/src/components/Queue.vue b/front/src/components/Queue.vue
index 232346bb1..019c82803 100644
--- a/front/src/components/Queue.vue
+++ b/front/src/components/Queue.vue
@@ -1,77 +1,148 @@
 <template>
-  <section class="main with-background component-queue" :aria-label="labels.queue">
+  <section
+    class="main with-background component-queue"
+    :aria-label="labels.queue"
+  >
     <div :class="['ui vertical stripe queue segment', playerFocused ? 'player-focused' : '']">
       <div class="ui fluid container">
-        <div class="ui stackable grid" id="queue-grid">
+        <div
+          id="queue-grid"
+          class="ui stackable grid"
+        >
           <div class="ui six wide column current-track">
-            <div class="ui basic segment" id="player">
+            <div
+              id="player"
+              class="ui basic segment"
+            >
               <template v-if="currentTrack">
-                <img ref="cover" alt="" v-if="currentTrack.cover && currentTrack.cover.urls.large_square_crop" :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.urls.large_square_crop)">
-                <img ref="cover" alt="" v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.urls.large_square_crop" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.urls.large_square_crop)">
-                <img class="ui image" alt="" v-else src="../assets/audio/default-cover.png">
+                <img
+                  v-if="currentTrack.cover && currentTrack.cover.urls.large_square_crop"
+                  ref="cover"
+                  alt=""
+                  :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.urls.large_square_crop)"
+                >
+                <img
+                  v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.urls.large_square_crop"
+                  ref="cover"
+                  alt=""
+                  :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.urls.large_square_crop)"
+                >
+                <img
+                  v-else
+                  class="ui image"
+                  alt=""
+                  src="../assets/audio/default-cover.png"
+                >
                 <h1 class="ui header">
                   <div class="content ellipsis">
-                    <router-link class="small header discrete link track" :to="{name: 'library.tracks.detail', params: {id: currentTrack.id }}">
+                    <router-link
+                      class="small header discrete link track"
+                      :to="{name: 'library.tracks.detail', params: {id: currentTrack.id }}"
+                    >
                       {{ currentTrack.title }}
                     </router-link>
                     <div class="sub header ellipsis">
-                      <router-link class="discrete link artist" :to="{name: 'library.artists.detail', params: {id: currentTrack.artist.id }}">{{ currentTrack.artist.name }}</router-link>
-                      <template v-if="currentTrack.album"> /
-                        <router-link class="discrete link album" :to="{name: 'library.albums.detail', params: {id: currentTrack.album.id }}">{{ currentTrack.album.title }}</router-link>
+                      <router-link
+                        class="discrete link artist"
+                        :to="{name: 'library.artists.detail', params: {id: currentTrack.artist.id }}"
+                      >
+                        {{ currentTrack.artist.name }}
+                      </router-link>
+                      <template v-if="currentTrack.album">
+                        /
+                        <router-link
+                          class="discrete link album"
+                          :to="{name: 'library.albums.detail', params: {id: currentTrack.album.id }}"
+                        >
+                          {{ currentTrack.album.title }}
+                        </router-link>
                       </template>
                     </div>
                   </div>
                 </h1>
-                <div class="ui small warning message" v-if="currentTrack && errored">
+                <div
+                  v-if="currentTrack && errored"
+                  class="ui small warning message"
+                >
                   <h3 class="header">
-                    <translate translate-context="Sidebar/Player/Error message.Title">The track cannot be loaded</translate>
+                    <translate translate-context="Sidebar/Player/Error message.Title">
+                      The track cannot be loaded
+                    </translate>
                   </h3>
                   <p v-if="hasNext && playing && $store.state.player.errorCount < $store.state.player.maxConsecutiveErrors">
-                    <translate translate-context="Sidebar/Player/Error message.Paragraph">The next track will play automatically in a few seconds…</translate>
-                    <i class="loading spinner icon"></i>
+                    <translate translate-context="Sidebar/Player/Error message.Paragraph">
+                      The next track will play automatically in a few seconds…
+                    </translate>
+                    <i class="loading spinner icon" />
                   </p>
                   <p>
-                    <translate translate-context="Sidebar/Player/Error message.Paragraph">You may have a connectivity issue.</translate>
+                    <translate translate-context="Sidebar/Player/Error message.Paragraph">
+                      You may have a connectivity issue.
+                    </translate>
                   </p>
                 </div>
                 <div class="additional-controls tablet-and-below">
                   <track-favorite-icon
                     v-if="$store.state.auth.authenticated"
-                    :track="currentTrack"></track-favorite-icon>
+                    :track="currentTrack"
+                  />
                   <track-playlist-icon
                     v-if="$store.state.auth.authenticated"
-                    :track="currentTrack"></track-playlist-icon>
+                    :track="currentTrack"
+                  />
                   <button
                     v-if="$store.state.auth.authenticated"
-                    @click="$store.dispatch('moderation/hide', {type: 'artist', target: currentTrack.artist})"
                     :class="['ui', 'really', 'basic', 'circular', 'icon', 'button']"
                     :aria-label="labels.addArtistContentFilter"
-                    :title="labels.addArtistContentFilter">
-                    <i :class="['eye slash outline', 'basic', 'icon']"></i>
+                    :title="labels.addArtistContentFilter"
+                    @click="$store.dispatch('moderation/hide', {type: 'artist', target: currentTrack.artist})"
+                  >
+                    <i :class="['eye slash outline', 'basic', 'icon']" />
                   </button>
                 </div>
                 <div class="progress-wrapper">
-                  <div class="progress-area" v-if="currentTrack && !errored">
+                  <div
+                    v-if="currentTrack && !errored"
+                    class="progress-area"
+                  >
                     <div
                       ref="progress"
                       :class="['ui', 'small', 'vibrant', {'indicating': isLoadingAudio}, 'progress']"
-                      @click="touchProgress">
-                      <div class="buffer bar" :data-percent="bufferProgress" :style="{ 'width': bufferProgress + '%' }"></div>
-                      <div class="position bar" :data-percent="progress" :style="{ 'width': progress + '%' }"></div>
+                      @click="touchProgress"
+                    >
+                      <div
+                        class="buffer bar"
+                        :data-percent="bufferProgress"
+                        :style="{ 'width': bufferProgress + '%' }"
+                      />
+                      <div
+                        class="position bar"
+                        :data-percent="progress"
+                        :style="{ 'width': progress + '%' }"
+                      />
                     </div>
                   </div>
-                  <div class="progress-area" v-else>
+                  <div
+                    v-else
+                    class="progress-area"
+                  >
                     <div
                       ref="progress"
-                      :class="['ui', 'small', 'vibrant', 'progress']">
-                      <div class="buffer bar"></div>
-                      <div class="position bar"></div>
+                      :class="['ui', 'small', 'vibrant', 'progress']"
+                    >
+                      <div class="buffer bar" />
+                      <div class="position bar" />
                     </div>
                   </div>
                   <div class="progress">
                     <template v-if="!isLoadingAudio">
-                      <a href="" :aria-label="labels.restart" class="left floated timer discrete start" @click.prevent="setCurrentTime(0)">{{currentTimeFormatted}}</a>
-                      <span class="right floated timer total">{{durationFormatted}}</span>
+                      <a
+                        href=""
+                        :aria-label="labels.restart"
+                        class="left floated timer discrete start"
+                        @click.prevent="setCurrentTime(0)"
+                      >{{ currentTimeFormatted }}</a>
+                      <span class="right floated timer total">{{ durationFormatted }}</span>
                     </template>
                     <template v-else>
                       <span class="left floated timer">00:00</span>
@@ -80,45 +151,47 @@
                   </div>
                 </div>
                 <div class="player-controls tablet-and-below">
-                  <template>
-                    <span
-                      role="button"
-                      :title="labels.previousTrack"
-                      :aria-label="labels.previousTrack"
-                      class="control"
-                      @click.prevent.stop="$store.dispatch('queue/previous')"
-                      :disabled="emptyQueue">
-                        <i :class="['ui', 'backward step', {'disabled': emptyQueue}, 'icon']"></i>
-                    </span>
+                  <span
+                    role="button"
+                    :title="labels.previousTrack"
+                    :aria-label="labels.previousTrack"
+                    class="control"
+                    :disabled="emptyQueue"
+                    @click.prevent.stop="$store.dispatch('queue/previous')"
+                  >
+                    <i :class="['ui', 'backward step', {'disabled': emptyQueue}, 'icon']" />
+                  </span>
 
-                    <span
-                      role="button"
-                      v-if="!playing"
-                      :title="labels.play"
-                      :aria-label="labels.play"
-                      @click.prevent.stop="resumePlayback"
-                      class="control">
-                        <i :class="['ui', 'play', {'disabled': !currentTrack}, 'icon']"></i>
-                    </span>
-                    <span
-                      role="button"
-                      v-else
-                      :title="labels.pause"
-                      :aria-label="labels.pause"
-                      @click.prevent.stop="pausePlayback"
-                      class="control">
-                        <i :class="['ui', 'pause', {'disabled': !currentTrack}, 'icon']"></i>
-                    </span>
-                    <span
-                      role="button"
-                      :title="labels.next"
-                      :aria-label="labels.next"
-                      class="control"
-                      @click.prevent.stop="$store.dispatch('queue/next')"
-                      :disabled="!hasNext">
-                        <i :class="['ui', {'disabled': !hasNext}, 'forward step', 'icon']" ></i>
-                    </span>
-                  </template>
+                  <span
+                    v-if="!playing"
+                    role="button"
+                    :title="labels.play"
+                    :aria-label="labels.play"
+                    class="control"
+                    @click.prevent.stop="resumePlayback"
+                  >
+                    <i :class="['ui', 'play', {'disabled': !currentTrack}, 'icon']" />
+                  </span>
+                  <span
+                    v-else
+                    role="button"
+                    :title="labels.pause"
+                    :aria-label="labels.pause"
+                    class="control"
+                    @click.prevent.stop="pausePlayback"
+                  >
+                    <i :class="['ui', 'pause', {'disabled': !currentTrack}, 'icon']" />
+                  </span>
+                  <span
+                    role="button"
+                    :title="labels.next"
+                    :aria-label="labels.next"
+                    class="control"
+                    :disabled="!hasNext"
+                    @click.prevent.stop="$store.dispatch('queue/next')"
+                  >
+                    <i :class="['ui', {'disabled': !hasNext}, 'forward step', 'icon']" />
+                  </span>
                 </div>
               </template>
             </div>
@@ -129,20 +202,30 @@
                 <div class="content">
                   <button
                     class="ui right floated basic button"
-                    @click="$store.commit('ui/queueFocused', null)">
-                      <translate translate-context="*/Queue/*/Verb">Close</translate>
+                    @click="$store.commit('ui/queueFocused', null)"
+                  >
+                    <translate translate-context="*/Queue/*/Verb">
+                      Close
+                    </translate>
                   </button>
                   <button
                     class="ui right floated basic button danger"
-                    @click="$store.dispatch('queue/clean')">
-                      <translate translate-context="*/Queue/*/Verb">Clear</translate>
+                    @click="$store.dispatch('queue/clean')"
+                  >
+                    <translate translate-context="*/Queue/*/Verb">
+                      Clear
+                    </translate>
                   </button>
                   {{ labels.queue }}
                   <div class="sub header">
                     <div>
-                      <translate translate-context="Sidebar/Queue/Text" :translate-params="{index: queue.currentIndex + 1, length: queue.tracks.length}">
+                      <translate
+                        translate-context="Sidebar/Queue/Text"
+                        :translate-params="{index: queue.currentIndex + 1, length: queue.tracks.length}"
+                      >
                         Track %{ index } of %{ length }
-                      </translate><template v-if="!$store.state.radios.running"> -
+                      </translate><template v-if="!$store.state.radios.running">
+                        -
                         <span :title="labels.duration">
                           {{ timeLeft }}
                         </span>
@@ -153,22 +236,53 @@
               </h2>
             </div>
             <table class="ui compact very basic fixed single line selectable unstackable table">
-              <draggable v-model="tracks" tag="tbody" @update="reorder" handle=".handle">
+              <draggable
+                v-model="tracks"
+                tag="tbody"
+                handle=".handle"
+                @update="reorder"
+              >
                 <tr
                   v-for="(track, index) in tracks"
                   :key="index"
-                  :class="['queue-item', {'active': index === queue.currentIndex}]">
+                  :class="['queue-item', {'active': index === queue.currentIndex}]"
+                >
                   <td class="handle">
-                    <i class="grip lines icon"></i>
+                    <i class="grip lines icon" />
                   </td>
-                  <td class="image-cell" @click="$store.dispatch('queue/currentIndex', index)">
-                    <img class="ui mini image" alt="" v-if="track.cover && track.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](track.cover.urls.medium_square_crop)">
-                    <img class="ui mini image" alt="" v-else-if="track.album && track.album.cover && track.album.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](track.album.cover.urls.medium_square_crop)">
-                    <img class="ui mini image" alt="" v-else src="../assets/audio/default-cover.png">
+                  <td
+                    class="image-cell"
+                    @click="$store.dispatch('queue/currentIndex', index)"
+                  >
+                    <img
+                      v-if="track.cover && track.cover.urls.original"
+                      class="ui mini image"
+                      alt=""
+                      :src="$store.getters['instance/absoluteUrl'](track.cover.urls.medium_square_crop)"
+                    >
+                    <img
+                      v-else-if="track.album && track.album.cover && track.album.cover.urls.original"
+                      class="ui mini image"
+                      alt=""
+                      :src="$store.getters['instance/absoluteUrl'](track.album.cover.urls.medium_square_crop)"
+                    >
+                    <img
+                      v-else
+                      class="ui mini image"
+                      alt=""
+                      src="../assets/audio/default-cover.png"
+                    >
                   </td>
-                  <td colspan="3" @click="$store.dispatch('queue/currentIndex', index)">
-                    <button class="title reset ellipsis" :title="track.title" :aria-label="labels.selectTrack">
-                      <strong>{{ track.title }}</strong><br />
+                  <td
+                    colspan="3"
+                    @click="$store.dispatch('queue/currentIndex', index)"
+                  >
+                    <button
+                      class="title reset ellipsis"
+                      :title="track.title"
+                      :aria-label="labels.selectTrack"
+                    >
+                      <strong>{{ track.title }}</strong><br>
                       <span>
                         {{ track.artist.name }}
                       </span>
@@ -181,23 +295,44 @@
                   </td>
                   <td class="controls">
                     <template v-if="$store.getters['favorites/isFavorite'](track.id)">
-                      <i class="pink heart icon"></i>
+                      <i class="pink heart icon" />
                     </template>
-                    <button :aria-label="labels.removeFromQueue" :title="labels.removeFromQueue" @click.stop="cleanTrack(index)" :class="['ui', 'really', 'tiny', 'basic', 'circular', 'icon', 'button']">
-                      <i class="x icon"></i>
+                    <button
+                      :aria-label="labels.removeFromQueue"
+                      :title="labels.removeFromQueue"
+                      :class="['ui', 'really', 'tiny', 'basic', 'circular', 'icon', 'button']"
+                      @click.stop="cleanTrack(index)"
+                    >
+                      <i class="x icon" />
                     </button>
                   </td>
                 </tr>
               </draggable>
             </table>
 
-            <div v-if="$store.state.radios.running" class="ui info message">
+            <div
+              v-if="$store.state.radios.running"
+              class="ui info message"
+            >
               <div class="content">
                 <h3 class="header">
-                  <i class="feed icon"></i> <translate translate-context="Sidebar/Player/Title">You have a radio playing</translate>
+                  <i class="feed icon" /> <translate translate-context="Sidebar/Player/Title">
+                    You have a radio playing
+                  </translate>
                 </h3>
-                <p><translate translate-context="Sidebar/Player/Paragraph">New tracks will be appended here automatically.</translate></p>
-                <button @click="$store.dispatch('radios/stop')" class="ui basic primary button"><translate translate-context="*/Player/Button.Label/Short, Verb">Stop radio</translate></button>
+                <p>
+                  <translate translate-context="Sidebar/Player/Paragraph">
+                    New tracks will be appended here automatically.
+                  </translate>
+                </p>
+                <button
+                  class="ui basic primary button"
+                  @click="$store.dispatch('radios/stop')"
+                >
+                  <translate translate-context="*/Player/Button.Label/Short, Verb">
+                    Stop radio
+                  </translate>
+                </button>
               </div>
             </div>
           </div>
@@ -229,16 +364,6 @@ export default {
       time
     }
   },
-  mounted () {
-    this.focusTrap = createFocusTrap(this.$el, { allowOutsideClick: () => { return true } })
-    this.focusTrap.activate()
-    this.$nextTick(() => {
-      setTimeout(() => {
-        this.scrollToCurrent()
-        // delay is to let transition work
-      }, 400)
-    })
-  },
   computed: {
     ...mapState({
       currentIndex: state => state.queue.currentIndex,
@@ -298,6 +423,46 @@ export default {
       return this.$store.state.ui.queueFocused === 'player'
     }
   },
+  watch: {
+    '$store.state.ui.queueFocused': {
+      handler (v) {
+        if (v === 'queue') {
+          this.$nextTick(() => {
+            this.scrollToCurrent()
+          })
+        }
+      },
+      immediate: true
+    },
+    '$store.state.queue.currentIndex': {
+      handler () {
+        this.$nextTick(() => {
+          this.scrollToCurrent()
+        })
+      }
+    },
+    '$store.state.queue.tracks': {
+      handler (v) {
+        if (!v || v.length === 0) {
+          this.$store.commit('ui/queueFocused', null)
+        }
+      },
+      immediate: true
+    },
+    '$route.fullPath' () {
+      this.$store.commit('ui/queueFocused', null)
+    }
+  },
+  mounted () {
+    this.focusTrap = createFocusTrap(this.$el, { allowOutsideClick: () => { return true } })
+    this.focusTrap.activate()
+    this.$nextTick(() => {
+      setTimeout(() => {
+        this.scrollToCurrent()
+        // delay is to let transition work
+      }, 400)
+    })
+  },
   methods: {
     ...mapActions({
       cleanTrack: 'queue/cleanTrack',
@@ -348,36 +513,6 @@ export default {
         })
       }, 100)
     }
-  },
-  watch: {
-    '$store.state.ui.queueFocused': {
-      handler (v) {
-        if (v === 'queue') {
-          this.$nextTick(() => {
-            this.scrollToCurrent()
-          })
-        }
-      },
-      immediate: true
-    },
-    '$store.state.queue.currentIndex': {
-      handler () {
-        this.$nextTick(() => {
-          this.scrollToCurrent()
-        })
-      }
-    },
-    '$store.state.queue.tracks': {
-      handler (v) {
-        if (!v || v.length === 0) {
-          this.$store.commit('ui/queueFocused', null)
-        }
-      },
-      immediate: true
-    },
-    '$route.fullPath' () {
-      this.$store.commit('ui/queueFocused', null)
-    }
   }
 }
 </script>
diff --git a/front/src/components/RemoteSearchForm.vue b/front/src/components/RemoteSearchForm.vue
index 880a49c55..22482771a 100644
--- a/front/src/components/RemoteSearchForm.vue
+++ b/front/src/components/RemoteSearchForm.vue
@@ -1,19 +1,51 @@
 <template>
-  <div v-if="type === 'both' || type === undefined" class="two ui buttons">
-    <button class="ui left floated labeled icon button" @click.prevent="changeType('rss')"><i class="feed icon"></i>
-      <translate translate-context="Content/Search/Input.Label/Noun">RSS</translate>
+  <div
+    v-if="type === 'both' || type === undefined"
+    class="two ui buttons"
+  >
+    <button
+      class="ui left floated labeled icon button"
+      @click.prevent="changeType('rss')"
+    >
+      <i class="feed icon" />
+      <translate translate-context="Content/Search/Input.Label/Noun">
+        RSS
+      </translate>
     </button>
-    <div class="or"></div>
-    <button class="ui right floated right labeled icon button" @click.prevent="changeType('artists')"><i class="globe icon"></i>
-      <translate translate-context="Content/Search/Input.Label/Noun">Fediverse</translate>
+    <div class="or" />
+    <button
+      class="ui right floated right labeled icon button"
+      @click.prevent="changeType('artists')"
+    >
+      <i class="globe icon" />
+      <translate translate-context="Content/Search/Input.Label/Noun">
+        Fediverse
+      </translate>
     </button>
   </div>
   <div v-else>
-    <form id="remote-search" :class="['ui', {loading: isLoading}, 'form']" @submit.stop.prevent="submit">
-      <div v-if="errors.length > 0" role="alert" class="ui negative message">
-        <h3 class="header"><translate translate-context="Content/*/Error message.Title">Error while fetching object</translate></h3>
+    <form
+      id="remote-search"
+      :class="['ui', {loading: isLoading}, 'form']"
+      @submit.stop.prevent="submit"
+    >
+      <div
+        v-if="errors.length > 0"
+        role="alert"
+        class="ui negative message"
+      >
+        <h3 class="header">
+          <translate translate-context="Content/*/Error message.Title">
+            Error while fetching object
+          </translate>
+        </h3>
         <ul class="list">
-          <li v-for="error in errors">{{ error }}</li>
+          <li
+            v-for="(error, key) in errors"
+            :key="key"
+          >
+            {{ error }}
+          </li>
         </ul>
       </div>
       <div class="ui required field">
@@ -21,19 +53,45 @@
           {{ labels.fieldLabel }}
         </label>
         <p v-if="type === 'rss'">
-          <translate translate-context="Content/Fetch/Paragraph">Use this form to subscribe to an RSS feed from its URL.</translate>
+          <translate translate-context="Content/Fetch/Paragraph">
+            Use this form to subscribe to an RSS feed from its URL.
+          </translate>
         </p>
         <p v-else-if="type === 'artists'">
-          <translate translate-context="Content/Fetch/Paragraph">Use this form to subscribe to a channel hosted somewhere else on the Fediverse.</translate>
+          <translate translate-context="Content/Fetch/Paragraph">
+            Use this form to subscribe to a channel hosted somewhere else on the Fediverse.
+          </translate>
         </p>
-        <input type="text" name="object-id" id="object-id" :placeholder="labels.fieldPlaceholder" v-model="id" required>
+        <input
+          id="object-id"
+          v-model="id"
+          type="text"
+          name="object-id"
+          :placeholder="labels.fieldPlaceholder"
+          required
+        >
       </div>
-      <button v-if="showSubmit" type="submit" :class="['ui', 'primary', {loading: isLoading}, 'button']" :disabled="isLoading || !id || id.length === 0">
-        <translate translate-context="Content/Search/Input.Label/Noun">Search</translate>
+      <button
+        v-if="showSubmit"
+        type="submit"
+        :class="['ui', 'primary', {loading: isLoading}, 'button']"
+        :disabled="isLoading || !id || id.length === 0"
+      >
+        <translate translate-context="Content/Search/Input.Label/Noun">
+          Search
+        </translate>
       </button>
     </form>
-    <div v-if="!isLoading && fetch && fetch.status === 'finished' && !redirectRoute" role="alert" class="ui warning message">
-      <p><translate translate-context="Content/*/Error message.Title">This kind of object isn't supported yet</translate></p>
+    <div
+      v-if="!isLoading && fetch && fetch.status === 'finished' && !redirectRoute"
+      role="alert"
+      class="ui warning message"
+    >
+      <p>
+        <translate translate-context="Content/*/Error message.Title">
+          This kind of object isn't supported yet
+        </translate>
+      </p>
     </div>
   </div>
 </template>
@@ -42,85 +100,101 @@ import axios from 'axios'
 
 export default {
   props: {
-    initialId: { type: String, required: false},
-    type: { type: String, required: false},
-    redirect: { type: Boolean, default: true},
-    showSubmit: { type: Boolean, default: true},
-    standalone: { type: Boolean, default: true},
+    initialId: { type: String, required: false, default: '' },
+    initialType: { type: String, required: false, default: '' },
+    redirect: { type: Boolean, default: true },
+    showSubmit: { type: Boolean, default: true },
+    standalone: { type: Boolean, default: true }
   },
 
   data () {
     return {
+      type: this.initialType,
       id: this.initialId,
       fetch: null,
       obj: null,
       isLoading: false,
-      errors: [],
-    }
-  },
-  created () {
-    if (this.id) {
-      if (this.type === 'rss') {
-        this.rssSubscribe()
-      } else if (this.type === 'artists') {
-        this.createFetch()
-      }
+      errors: []
     }
   },
   computed: {
-    labels() {
-      let title = ""
-      let fieldLabel = ""
-      let fieldPlaceholder = ""
-      if (this.type === "rss") {
-        title = this.$pgettext('Head/Fetch/Title', "Subscribe to a podcast RSS feed")
-        fieldLabel = this.$pgettext('*/*/*', "RSS feed location")
-        fieldPlaceholder = this.$pgettext('Head/Fetch/Field.Placeholder', "https://website.example.com/rss.xml")
+    labels () {
+      let title = ''
+      let fieldLabel = ''
+      let fieldPlaceholder = ''
+      if (this.type === 'rss') {
+        title = this.$pgettext('Head/Fetch/Title', 'Subscribe to a podcast RSS feed')
+        fieldLabel = this.$pgettext('*/*/*', 'RSS feed location')
+        fieldPlaceholder = this.$pgettext('Head/Fetch/Field.Placeholder', 'https://website.example.com/rss.xml')
       } else if (this.type === 'artists') {
-        title = this.$pgettext('Head/Fetch/Title', "Subscribe to a podcast hosted on the Fediverse")
-        fieldLabel = this.$pgettext('*/*/*', "Fediverse object")
-        fieldPlaceholder = this.$pgettext('Head/Fetch/Field.Placeholder', "@username@example.com")
+        title = this.$pgettext('Head/Fetch/Title', 'Subscribe to a podcast hosted on the Fediverse')
+        fieldLabel = this.$pgettext('*/*/*', 'Fediverse object')
+        fieldPlaceholder = this.$pgettext('Head/Fetch/Field.Placeholder', '@username@example.com')
       }
       return {
         title,
         fieldLabel,
-        fieldPlaceholder,
+        fieldPlaceholder
       }
     },
     objInfo () {
       if (this.fetch && this.fetch.status === 'finished') {
         return this.fetch.object
       }
+      return null
     },
     redirectRoute () {
       if (!this.objInfo) {
         return
       }
       switch (this.objInfo.type) {
-        case 'account':
-          let [username, domain] = this.objInfo.full_username.split('@')
-          return {name: 'profile.full', params: {username, domain}}
+        case 'account': {
+          const [username, domain] = this.objInfo.full_username.split('@')
+          return { name: 'profile.full', params: { username, domain } }
+        }
         case 'library':
-          return {name: 'library.detail', params: {id: this.objInfo.uuid}}
+          return { name: 'library.detail', params: { id: this.objInfo.uuid } }
         case 'artist':
-          return {name: 'library.artists.detail', params: {id: this.objInfo.id}}
+          return { name: 'library.artists.detail', params: { id: this.objInfo.id } }
         case 'album':
-          return {name: 'library.albums.detail', params: {id: this.objInfo.id}}
+          return { name: 'library.albums.detail', params: { id: this.objInfo.id } }
         case 'track':
-          return {name: 'library.tracks.detail', params: {id: this.objInfo.id}}
+          return { name: 'library.tracks.detail', params: { id: this.objInfo.id } }
         case 'upload':
-          return {name: 'library.uploads.detail', params: {id: this.objInfo.uuid}}
+          return { name: 'library.uploads.detail', params: { id: this.objInfo.uuid } }
         case 'channel':
-          return {name: 'channels.detail', params: {id: this.objInfo.uuid}}
+          return { name: 'channels.detail', params: { id: this.objInfo.uuid } }
 
         default:
-          break;
+          break
+      }
+      return null
+    }
+  },
+
+  watch: {
+    initialId (v) {
+      this.id = v
+      this.createFetch()
+    },
+    redirectRoute (v) {
+      if (v && this.redirect) {
+        this.$router.push(v)
+      }
+    }
+  },
+  created () {
+    if (this.id) {
+      if (this.type === 'rss') {
+        this.rssSubscribe()
+      } else if (this.type === 'artists') {
+        this.createFetch()
       }
     }
   },
 
   methods: {
-    changeType(newType) {
+    changeType (newType) {
       this.type = newType
     },
     submit () {
@@ -135,13 +209,13 @@ export default {
         return
       }
       if (this.standalone) {
-        this.$router.replace({name: "search", query: {id: this.id}})
+        this.$router.replace({ name: 'search', query: { id: this.id } })
       }
       this.fetch = null
-      let self = this
+      const self = this
       self.errors = []
       self.isLoading = true
-      let payload = {
+      const payload = {
         object: this.id
       }
 
@@ -150,7 +224,7 @@ export default {
         self.fetch = response.data
         if (self.fetch.status === 'errored' || self.fetch.status === 'skipped') {
           self.errors.push(
-            self.$pgettext("Content/*/Error message.Title", "This object cannot be retrieved")
+            self.$pgettext('Content/*/Error message.Title', 'This object cannot be retrieved')
           )
         }
       }, error => {
@@ -163,40 +237,27 @@ export default {
         return
       }
       if (this.standalone) {
-        this.$router.replace({name: "search", query: {id: this.id, type: 'rss'}})
+        this.$router.replace({ name: 'search', query: { id: this.id, type: 'rss' } })
       }
       this.fetch = null
-      let self = this
+      const self = this
       self.errors = []
       self.isLoading = true
-      let payload = {
+      const payload = {
         url: this.id
       }
 
       axios.post('channels/rss-subscribe/', payload).then((response) => {
         self.isLoading = false
-        self.$store.commit('channels/subscriptions', {uuid: response.data.channel.uuid, value: true})
+        self.$store.commit('channels/subscriptions', { uuid: response.data.channel.uuid, value: true })
         self.$emit('subscribed', response.data)
         if (self.redirect) {
-          self.$router.push({name: 'channels.detail', params: {id: response.data.channel.uuid}})
+          self.$router.push({ name: 'channels.detail', params: { id: response.data.channel.uuid } })
         }
-
       }, error => {
         self.isLoading = false
         self.errors = error.backendErrors
       })
-    },
-  },
-
-  watch: {
-    initialId (v) {
-      this.id = v
-      this.createFetch()
-    },
-    redirectRoute (v) {
-      if (v && this.redirect) {
-        this.$router.push(v)
-      }
     }
   }
 }
diff --git a/front/src/components/ServiceMessages.vue b/front/src/components/ServiceMessages.vue
index a7fad93a5..a7f990888 100644
--- a/front/src/components/ServiceMessages.vue
+++ b/front/src/components/ServiceMessages.vue
@@ -1,7 +1,11 @@
 <template>
   <div class="ui toast-container">
-    <message v-for="message in $store.state.ui.messages" :message="message" :key="message.key"></message>
-    <slot></slot>
+    <message
+      v-for="message in $store.state.ui.messages"
+      :key="message.key"
+      :message="message"
+    />
+    <slot />
   </div>
 </template>
 
diff --git a/front/src/components/SetInstanceModal.vue b/front/src/components/SetInstanceModal.vue
index d0d7b27f1..dc1b17f22 100644
--- a/front/src/components/SetInstanceModal.vue
+++ b/front/src/components/SetInstanceModal.vue
@@ -1,41 +1,105 @@
 <template>
-  <modal @update:show="$emit('update:show', $event); isError = false" :show="show">
-    <h3 class="header"><translate translate-context="Popup/Instance/Title">Choose your instance</translate></h3>
+  <modal
+    :show="show"
+    @update:show="$emit('update:show', $event); isError = false"
+  >
+    <h3 class="header">
+      <translate translate-context="Popup/Instance/Title">
+        Choose your instance
+      </translate>
+    </h3>
     <div class="scrolling content">
-      <div v-if="isError" role="alert" class="ui negative message">
-        <h4 class="header"><translate translate-context="Popup/Instance/Error message.Title">It is not possible to connect to the given URL</translate></h4>
+      <div
+        v-if="isError"
+        role="alert"
+        class="ui negative message"
+      >
+        <h4 class="header">
+          <translate translate-context="Popup/Instance/Error message.Title">
+            It is not possible to connect to the given URL
+          </translate>
+        </h4>
         <ul class="list">
-          <li><translate translate-context="Popup/Instance/Error message.List item">The server might be down</translate></li>
-          <li><translate translate-context="Popup/Instance/Error message.List item">The given address is not a Funkwhale server</translate></li>
+          <li>
+            <translate translate-context="Popup/Instance/Error message.List item">
+              The server might be down
+            </translate>
+          </li>
+          <li>
+            <translate translate-context="Popup/Instance/Error message.List item">
+              The given address is not a Funkwhale server
+            </translate>
+          </li>
         </ul>
       </div>
-      <form class="ui form" @submit.prevent="checkAndSwitch(instanceUrl)">
-        <p v-if="$store.state.instance.instanceUrl" class="description" translate-context="Popup/Login/Paragraph" v-translate="{url: $store.state.instance.instanceUrl, hostname: instanceHostname }">
-            You are currently connected to <a href="%{ url }" target="_blank">%{ hostname }&nbsp;<i class="external icon"></i></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted.
+      <form
+        class="ui form"
+        @submit.prevent="checkAndSwitch(instanceUrl)"
+      >
+        <p
+          v-if="$store.state.instance.instanceUrl"
+          v-translate="{url: $store.state.instance.instanceUrl, hostname: instanceHostname }"
+          class="description"
+          translate-context="Popup/Login/Paragraph"
+        >
+          You are currently connected to <a
+            href="%{ url }"
+            target="_blank"
+          >%{ hostname }&nbsp;<i class="external icon" /></a>. If you continue, you will be disconnected from your current instance and all your local data will be deleted.
         </p>
         <p v-else>
-          <translate translate-context="Popup/Instance/Paragraph">To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices.</translate>
+          <translate translate-context="Popup/Instance/Paragraph">
+            To continue, please select the Funkwhale instance you want to connect to. Enter the address directly, or select one of the suggested choices.
+          </translate>
         </p>
         <div class="field">
           <label for="instance-picker"><translate translate-context="Popup/Instance/Input.Label/Noun">Instance URL</translate></label>
           <div class="ui action input">
-            <input id ="instance-picker" type="text" v-model="instanceUrl" placeholder="https://funkwhale.server">
-            <button type="submit" :class="['ui', 'icon', {loading: isLoading}, 'button']">
-              <translate translate-context="*/*/Button.Label/Verb">Submit</translate>
+            <input
+              id="instance-picker"
+              v-model="instanceUrl"
+              type="text"
+              placeholder="https://funkwhale.server"
+            >
+            <button
+              type="submit"
+              :class="['ui', 'icon', {loading: isLoading}, 'button']"
+            >
+              <translate translate-context="*/*/Button.Label/Verb">
+                Submit
+              </translate>
             </button>
           </div>
         </div>
       </form>
-      <div class="ui hidden divider"></div>
-      <form class="ui form" @submit.prevent="">
+      <div class="ui hidden divider" />
+      <form
+        class="ui form"
+        @submit.prevent=""
+      >
         <div class="field">
-          <h4><translate translate-context="Popup/Instance/List.Label">Suggested choices</translate></h4>
-          <button v-for="url in suggestedInstances" @click="checkAndSwitch(url)" class="ui basic button">{{ url }}</button>
+          <h4>
+            <translate translate-context="Popup/Instance/List.Label">
+              Suggested choices
+            </translate>
+          </h4>
+          <button
+            v-for="(url, key) in suggestedInstances"
+            :key="key"
+            class="ui basic button"
+            @click="checkAndSwitch(url)"
+          >
+            {{ url }}
+          </button>
         </div>
       </form>
     </div>
     <div class="actions">
-      <button class="ui basic cancel button"><translate translate-context="*/*/Button.Label/Verb">Cancel</translate></button>
+      <button class="ui basic cancel button">
+        <translate translate-context="*/*/Button.Label/Verb">
+          Cancel
+        </translate>
+      </button>
     </div>
   </modal>
 </template>
@@ -43,25 +107,52 @@
 <script>
 import Modal from '@/components/semantic/Modal'
 import axios from 'axios'
-import _ from "@/lodash"
+import _ from '@/lodash'
 
 export default {
-  props: ['show'],
   components: {
-    Modal,
+    Modal
   },
-  data() {
+  props: { show: { type: Boolean, required: true } },
+  data () {
     return {
       instanceUrl: null,
       nodeinfo: null,
       isError: false,
       isLoading: false,
-      path: 'api/v1/instance/nodeinfo/2.0/',
+      path: 'api/v1/instance/nodeinfo/2.0/'
+    }
+  },
+  computed: {
+    suggestedInstances () {
+      const instances = this.$store.state.instance.knownInstances.slice(0)
+      if (this.$store.state.instance.frontSettings.defaultServerUrl) {
+        let serverUrl = this.$store.state.instance.frontSettings.defaultServerUrl
+        if (!serverUrl.endsWith('/')) {
+          serverUrl = serverUrl + '/'
+        }
+        instances.push(serverUrl)
+      }
+      const self = this
+      instances.push(this.$store.getters['instance/defaultUrl'](), 'https://demo.funkwhale.audio/')
+      return _.uniq(instances.filter((e) => { return e !== self.$store.state.instance.instanceUrl }))
+    },
+    instanceHostname () {
+      const url = this.$store.state.instance.instanceUrl
+      const parser = document.createElement('a')
+      parser.href = url
+      return parser.hostname
+    }
+  },
+  watch: {
+    '$store.state.instance.instanceUrl' () {
+      this.$store.dispatch('instance/fetchSettings')
+      this.fetchNodeInfo()
     }
   },
   methods: {
     fetchNodeInfo () {
-      let self = this
+      const self = this
       axios.get('instance/nodeinfo/2.0/').then(response => {
         self.nodeinfo = response.data
       })
@@ -71,7 +162,7 @@ export default {
       if (!urlFetch.endsWith('/')) {
         urlFetch = `${urlFetch}/${this.path}`
       } else {
-        urlFetch =  `${urlFetch}${this.path}`
+        urlFetch = `${urlFetch}${this.path}`
       }
       if (!urlFetch.startsWith('https://') && !urlFetch.startsWith('http://')) {
         urlFetch = `https://${urlFetch}`
@@ -79,14 +170,14 @@ export default {
       return urlFetch
     },
     requestDistantNodeInfo (url) {
-      var self = this
+      const self = this
       axios.get(this.fetchUrl(url)).then(function (response) {
         self.isLoading = false
-        if(!url.startsWith('https://') && !url.startsWith('http://')) {
+        if (!url.startsWith('https://') && !url.startsWith('http://')) {
           url = `https://${url}`
         }
         self.switchInstance(url)
-      }).catch(function (error) {
+      }).catch(function () {
         self.isLoading = false
         self.isError = true
       })
@@ -95,12 +186,12 @@ export default {
       // Here we disconnect from the current instance and reconnect to the new one. No check is performed…
       this.$emit('update:show', false)
       this.isError = false
-      let msg = this.$pgettext('*/Instance/Message', 'You are now using the Funkwhale instance at %{ url }')
+      const msg = this.$pgettext('*/Instance/Message', 'You are now using the Funkwhale instance at %{ url }')
       this.$store.commit('ui/addMessage', {
-        content: this.$gettextInterpolate(msg, {url: url}),
+        content: this.$gettextInterpolate(msg, { url: url }),
         date: new Date()
       })
-      let self = this
+      const self = this
       this.$nextTick(() => {
         self.$store.commit('instance/instanceUrl', null)
         self.$store.dispatch('instance/setUrl', url)
@@ -111,34 +202,7 @@ export default {
       this.isError = false // Clear error message if any…
       this.isLoading = true
       this.requestDistantNodeInfo(url)
-    },
-  },
-  computed: {
-    suggestedInstances () {
-      let instances = this.$store.state.instance.knownInstances.slice(0)
-      if (this.$store.state.instance.frontSettings.defaultServerUrl) {
-        let serverUrl = this.$store.state.instance.frontSettings.defaultServerUrl
-        if (!serverUrl.endsWith('/')) {
-          serverUrl = serverUrl + '/'
-        }
-        instances.push(serverUrl)
-      }
-      let self = this
-      instances.push(this.$store.getters['instance/defaultUrl'](), 'https://demo.funkwhale.audio/')
-      return _.uniq(instances.filter((e) => {return e != self.$store.state.instance.instanceUrl}))
-    },
-    instanceHostname() {
-      let url = this.$store.state.instance.instanceUrl
-      let parser = document.createElement("a")
-      parser.href = url
-      return parser.hostname
-    },
-  },
-  watch: {
-    '$store.state.instance.instanceUrl' () {
-      this.$store.dispatch('instance/fetchSettings')
-      this.fetchNodeInfo()
-    },
-  },
+    }
+  }
 }
 </script>
diff --git a/front/src/components/ShortcutsModal.vue b/front/src/components/ShortcutsModal.vue
index 2090f8a95..cba1a5128 100644
--- a/front/src/components/ShortcutsModal.vue
+++ b/front/src/components/ShortcutsModal.vue
@@ -1,42 +1,59 @@
 <template>
-  <modal @update:show="$emit('update:show', $event)" :show="show">
+  <modal
+    :show="show"
+    @update:show="$emit('update:show', $event)"
+  >
     <header class="header">
-      <translate translate-context="*/*/*/Noun">Keyboard shortcuts</translate>
+      <translate translate-context="*/*/*/Noun">
+        Keyboard shortcuts
+      </translate>
     </header>
     <section class="scrolling content">
       <div class="ui stackable two column grid">
         <div class="column">
           <table
-            class="ui compact basic table"
             v-for="section in player"
-            :key="section.title">
-          <caption>{{ section.title }}</caption>
-          <tbody>
-            <tr v-for="shortcut in section.shortcuts" :key="shortcut.summary">
-              <td>{{ shortcut.summary }}</td>
-              <td><span class="ui label">{{ shortcut.key }}</span></td>
-            </tr>
-          </tbody>
+            :key="section.title"
+            class="ui compact basic table"
+          >
+            <caption>{{ section.title }}</caption>
+            <tbody>
+              <tr
+                v-for="shortcut in section.shortcuts"
+                :key="shortcut.summary"
+              >
+                <td>{{ shortcut.summary }}</td>
+                <td><span class="ui label">{{ shortcut.key }}</span></td>
+              </tr>
+            </tbody>
           </table>
         </div>
         <div class="column">
           <table
-            class="ui compact basic table"
             v-for="section in general"
-            :key="section.title">
-          <caption>{{ section.title }}</caption>
-          <tbody>
-            <tr v-for="shortcut in section.shortcuts" :key="shortcut.summary">
-              <td>{{ shortcut.summary }}</td>
-              <td><span class="ui label">{{ shortcut.key }}</span></td>
-            </tr>
-          </tbody>
+            :key="section.title"
+            class="ui compact basic table"
+          >
+            <caption>{{ section.title }}</caption>
+            <tbody>
+              <tr
+                v-for="shortcut in section.shortcuts"
+                :key="shortcut.summary"
+              >
+                <td>{{ shortcut.summary }}</td>
+                <td><span class="ui label">{{ shortcut.key }}</span></td>
+              </tr>
+            </tbody>
           </table>
         </div>
       </div>
     </section>
     <footer class="actions">
-      <button class="ui basic cancel button"><translate translate-context="*/*/Button.Label/Verb">Close</translate></button>
+      <button class="ui basic cancel button">
+        <translate translate-context="*/*/Button.Label/Verb">
+          Close
+        </translate>
+      </button>
     </footer>
   </modal>
 </template>
@@ -44,10 +61,10 @@
 <script>
 
 export default {
-  props: ['show'],
   components: {
-    Modal:  () => import(/* webpackChunkName: "modal" */ "@/components/semantic/Modal"),
+    Modal: () => import(/* webpackChunkName: "modal" */ '@/components/semantic/Modal')
   },
+  props: { show: { type: Boolean, required: true } },
   computed: {
     general () {
       return [
@@ -65,9 +82,9 @@ export default {
             {
               key: 'esc',
               summary: this.$pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Unfocus searchbar')
-            },
+            }
           ]
-        },
+        }
       ]
     },
 
@@ -135,7 +152,7 @@ export default {
             {
               key: 'f',
               summary: this.$pgettext('Popup/Keyboard shortcuts/Table.Label/Verb', 'Toggle favorite')
-            },
+            }
           ]
         }
       ]
diff --git a/front/src/components/admin/SettingsGroup.vue b/front/src/components/admin/SettingsGroup.vue
index 332e35e6a..94e7620e8 100644
--- a/front/src/components/admin/SettingsGroup.vue
+++ b/front/src/components/admin/SettingsGroup.vue
@@ -1,86 +1,156 @@
 <template>
-  <form :id="group.id" class="ui form component-settings-group" @submit.prevent="save">
+  <form
+    :id="group.id"
+    class="ui form component-settings-group"
+    @submit.prevent="save"
+  >
     <div class="ui divider" />
-    <h3 class="ui header">{{ group.label }}</h3>
-    <div v-if="errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/*/Error message.Title">Error while saving settings</translate></h4>
+    <h3 class="ui header">
+      {{ group.label }}
+    </h3>
+    <div
+      v-if="errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/*/Error message.Title">
+          Error while saving settings
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
-    <div v-if="result" class="ui positive message">
-      <translate translate-context="Content/Settings/Paragraph">Settings updated successfully.</translate>
+    <div
+      v-if="result"
+      class="ui positive message"
+    >
+      <translate translate-context="Content/Settings/Paragraph">
+        Settings updated successfully.
+      </translate>
     </div>
-    <p v-if="group.help">{{ group.help }}</p>
-    <div v-for="setting in settings" class="ui field">
+    <p v-if="group.help">
+      {{ group.help }}
+    </p>
+    <div
+      v-for="(setting, key) in settings"
+      :key="key"
+      class="ui field"
+    >
       <template v-if="setting.field.widget.class !== 'CheckboxInput'">
         <label :for="setting.identifier">{{ setting.verbose_name }}</label>
-        <p v-if="setting.help_text">{{ setting.help_text }}</p>
+        <p v-if="setting.help_text">
+          {{ setting.help_text }}
+        </p>
       </template>
-      <content-form v-if="setting.fieldType === 'markdown'" v-model="values[setting.identifier]" v-bind="setting.fieldParams" />
+      <content-form
+        v-if="setting.fieldType === 'markdown'"
+        v-model="values[setting.identifier]"
+        v-bind="setting.fieldParams"
+      />
       <signup-form-builder
         v-else-if="setting.fieldType === 'formBuilder'"
         :value="values[setting.identifier]"
         :signup-approval-enabled="values.moderation__signup_approval_enabled"
-        @input="set(setting.identifier, $event)" />
+        @input="set(setting.identifier, $event)"
+      />
       <input
+        v-else-if="setting.field.widget.class === 'PasswordInput'"
         :id="setting.identifier"
+        v-model="values[setting.identifier]"
         :name="setting.identifier"
-        v-else-if="setting.field.widget.class === 'PasswordInput'"
         type="password"
         class="ui input"
-        v-model="values[setting.identifier]" />
+      >
       <input
+        v-else-if="setting.field.widget.class === 'TextInput'"
         :id="setting.identifier"
+        v-model="values[setting.identifier]"
         :name="setting.identifier"
-        v-else-if="setting.field.widget.class === 'TextInput'"
         type="text"
         class="ui input"
-        v-model="values[setting.identifier]" />
+      >
       <input
+        v-else-if="setting.field.class === 'IntegerField'"
         :id="setting.identifier"
+        v-model.number="values[setting.identifier]"
         :name="setting.identifier"
-        v-else-if="setting.field.class === 'IntegerField'"
         type="number"
         class="ui input"
-        v-model.number="values[setting.identifier]" />
+      >
       <textarea
+        v-else-if="setting.field.widget.class === 'Textarea'"
         :id="setting.identifier"
+        v-model="values[setting.identifier]"
         :name="setting.identifier"
-        v-else-if="setting.field.widget.class === 'Textarea'"
         type="text"
         class="ui input"
-        v-model="values[setting.identifier]" />
-      <div v-else-if="setting.field.widget.class === 'CheckboxInput'" class="ui toggle checkbox">
+      />
+      <div
+        v-else-if="setting.field.widget.class === 'CheckboxInput'"
+        class="ui toggle checkbox"
+      >
         <input
           :id="setting.identifier"
-          :name="setting.identifier"
           v-model="values[setting.identifier]"
-          type="checkbox" />
+          :name="setting.identifier"
+          type="checkbox"
+        >
         <label :for="setting.identifier">{{ setting.verbose_name }}</label>
-        <p v-if="setting.help_text">{{ setting.help_text }}</p>
+        <p v-if="setting.help_text">
+          {{ setting.help_text }}
+        </p>
       </div>
       <select
-        :id="setting.identifier"
         v-else-if="setting.field.class === 'MultipleChoiceField'"
+        :id="setting.identifier"
         v-model="values[setting.identifier]"
         multiple
-        class="ui search selection dropdown">
-        <option v-for="v in setting.additional_data.choices" :value="v[0]">{{ v[1] }}</option>
+        class="ui search selection dropdown"
+      >
+        <option
+          v-for="(v, index) in setting.additional_data.choices"
+          :key="index"
+          :value="v[0]"
+        >
+          {{ v[1] }}
+        </option>
       </select>
       <div v-else-if="setting.field.widget.class === 'ImageWidget'">
-        <input :id="setting.identifier" type="file" :ref="setting.identifier">
+        <input
+          :id="setting.identifier"
+          :ref="setting.identifier"
+          type="file"
+        >
         <div v-if="values[setting.identifier]">
-          <div class="ui hidden divider"></div>
-          <h3 class="ui header"><translate translate-context="Content/Settings/Title/Noun">Current image</translate></h3>
-          <img class="ui image" alt="" v-if="values[setting.identifier]" :src="$store.getters['instance/absoluteUrl'](values[setting.identifier])" />
+          <div class="ui hidden divider" />
+          <h3 class="ui header">
+            <translate translate-context="Content/Settings/Title/Noun">
+              Current image
+            </translate>
+          </h3>
+          <img
+            v-if="values[setting.identifier]"
+            class="ui image"
+            alt=""
+            :src="$store.getters['instance/absoluteUrl'](values[setting.identifier])"
+          >
         </div>
       </div>
     </div>
     <button
       type="submit"
-      :class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']">
-        <translate translate-context="Content/*/Button.Label/Verb">Save</translate>
+      :class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
+    >
+      <translate translate-context="Content/*/Button.Label/Verb">
+        Save
+      </translate>
     </button>
   </form>
 </template>
@@ -91,12 +161,12 @@ import axios from 'axios'
 import lodash from '@/lodash'
 
 export default {
-  props: {
-    group: {type: Object, required: true},
-    settingsData: {type: Array, required: true}
-  },
   components: {
-    SignupFormBuilder:  () => import(/* webpackChunkName: "signup-form-builder" */ "@/components/admin/SignupFormBuilder"),
+    SignupFormBuilder: () => import(/* webpackChunkName: "signup-form-builder" */ '@/components/admin/SignupFormBuilder')
+  },
+  props: {
+    group: { type: Object, required: true },
+    settingsData: { type: Array, required: true }
   },
   data () {
     return {
@@ -106,28 +176,44 @@ export default {
       isLoading: false
     }
   },
+  computed: {
+    settings () {
+      const byIdentifier = {}
+      this.settingsData.forEach(e => {
+        byIdentifier[e.identifier] = e
+      })
+      return this.group.settings.map(e => {
+        return { ...byIdentifier[e.name], fieldType: e.fieldType, fieldParams: e.fieldParams || {} }
+      })
+    },
+    fileSettings () {
+      return this.settings.filter((s) => {
+        return s.field.widget.class === 'ImageWidget'
+      })
+    }
+  },
   created () {
-    let self = this
+    const self = this
     this.settings.forEach(e => {
       self.values[e.identifier] = e.value
     })
   },
   methods: {
     save () {
-      let self = this
+      const self = this
       this.isLoading = true
       self.errors = []
       self.result = null
       let postData = self.values
       let contentType = 'application/json'
-      let fileSettingsIDs = this.fileSettings.map((s) => {return s.identifier})
+      const fileSettingsIDs = this.fileSettings.map((s) => { return s.identifier })
       if (fileSettingsIDs.length > 0) {
         contentType = 'multipart/form-data'
         postData = new FormData()
         this.settings.forEach((s) => {
           if (fileSettingsIDs.indexOf(s.identifier) > -1) {
-            let input = self.$refs[s.identifier][0]
-            let files = input.files
+            const input = self.$refs[s.identifier][0]
+            const files = input.files
             console.log('ref', input, files)
             if (files && files.length > 0) {
               postData.append(s.identifier, files[0])
@@ -139,8 +225,8 @@ export default {
       }
       axios.post('instance/admin/settings/bulk/', postData, {
         headers: {
-          'Content-Type': contentType,
-        },
+          'Content-Type': contentType
+        }
       }).then((response) => {
         self.result = true
         response.data.forEach((s) => {
@@ -158,22 +244,6 @@ export default {
       this.values = lodash.cloneDeep(this.values)
       this.$set(this.values, key, value)
     }
-  },
-  computed: {
-    settings () {
-      let byIdentifier = {}
-      this.settingsData.forEach(e => {
-        byIdentifier[e.identifier] = e
-      })
-      return this.group.settings.map(e => {
-        return {...byIdentifier[e.name], fieldType: e.fieldType, fieldParams: e.fieldParams || {}}
-      })
-    },
-    fileSettings () {
-      return this.settings.filter((s) => {
-        return s.field.widget.class === 'ImageWidget'
-      })
-    }
   }
 }
 </script>
diff --git a/front/src/components/admin/SignupFormBuilder.vue b/front/src/components/admin/SignupFormBuilder.vue
index 3724b883e..1eb29f632 100644
--- a/front/src/components/admin/SignupFormBuilder.vue
+++ b/front/src/components/admin/SignupFormBuilder.vue
@@ -1,116 +1,173 @@
 <template>
   <div>
-
     <div class="ui top attached tabular menu">
-      <button :class="[{active: !isPreviewing}, 'item']" @click.stop.prevent="isPreviewing = false">
-        <translate translate-context="Content/*/Button.Label/Verb">Edit form</translate>
+      <button
+        :class="[{active: !isPreviewing}, 'item']"
+        @click.stop.prevent="isPreviewing = false"
+      >
+        <translate translate-context="Content/*/Button.Label/Verb">
+          Edit form
+        </translate>
       </button>
-      <button :class="[{active: isPreviewing}, 'item']" @click.stop.prevent="isPreviewing = true">
-        <translate translate-context="*/Form/Menu.item">Preview form</translate>
+      <button
+        :class="[{active: isPreviewing}, 'item']"
+        @click.stop.prevent="isPreviewing = true"
+      >
+        <translate translate-context="*/Form/Menu.item">
+          Preview form
+        </translate>
       </button>
     </div>
-    <div v-if="isPreviewing" class="ui bottom attached segment">
+    <div
+      v-if="isPreviewing"
+      class="ui bottom attached segment"
+    >
       <signup-form
         :customization="local"
         :signup-approval-enabled="signupApprovalEnabled"
-        :fetch-description-html="true"></signup-form>
-      <div class="ui clearing hidden divider"></div>
+        :fetch-description-html="true"
+      />
+      <div class="ui clearing hidden divider" />
     </div>
-    <div v-else class="ui bottom attached segment">
+    <div
+      v-else
+      class="ui bottom attached segment"
+    >
       <div class="field">
         <label for="help-text">
           <translate translate-context="*/*/Label">Help text</translate>
         </label>
         <p>
-          <translate translate-context="*/*/Help">An optional text to be displayed at the start of the sign-up form.</translate>
+          <translate translate-context="*/*/Help">
+            An optional text to be displayed at the start of the sign-up form.
+          </translate>
         </p>
         <content-form
           field-id="help-text"
           :permissive="true"
           :value="(local.help_text || {}).text"
-          @input="update('help_text.text', $event)"></content-form>
+          @input="update('help_text.text', $event)"
+        />
       </div>
       <div class="field">
         <label>
           <translate translate-context="*/*/Label">Additional fields</translate>
         </label>
         <p>
-          <translate translate-context="*/*/Help">Additional form fields to be displayed in the form. Only shown if manual sign-up validation is enabled.</translate>
+          <translate translate-context="*/*/Help">
+            Additional form fields to be displayed in the form. Only shown if manual sign-up validation is enabled.
+          </translate>
         </p>
         <table v-if="local.fields.length > 0">
           <thead>
             <tr>
               <th>
-                <translate translate-context="*/*/Form-builder,Help">Field label</translate>
+                <translate translate-context="*/*/Form-builder,Help">
+                  Field label
+                </translate>
               </th>
               <th>
-                <translate translate-context="*/*/Form-builder,Help">Field type</translate>
+                <translate translate-context="*/*/Form-builder,Help">
+                  Field type
+                </translate>
               </th>
               <th>
-                <translate translate-context="*/*/Form-builder,Help">Required</translate>
+                <translate translate-context="*/*/Form-builder,Help">
+                  Required
+                </translate>
               </th>
               <th><span class="visually-hidden"><translate translate-context="*/*/Form-builder,Help">Actions</translate></span></th>
             </tr>
           </thead>
           <tbody>
-            <tr v-for="(field, idx) in local.fields">
+            <tr
+              v-for="(field, idx) in local.fields"
+              :key="idx"
+            >
               <td>
-                <input type="text" v-model="field.label" required>
+                <input
+                  v-model="field.label"
+                  type="text"
+                  required
+                >
               </td>
               <td>
                 <select v-model="field.input_type">
                   <option value="short_text">
-                    <translate translate-context="*/*/Form-builder">Short text</translate>
+                    <translate translate-context="*/*/Form-builder">
+                      Short text
+                    </translate>
                   </option>
                   <option value="long_text">
-                    <translate translate-context="*/*/Form-builder">Long text</translate>
+                    <translate translate-context="*/*/Form-builder">
+                      Long text
+                    </translate>
                   </option>
                 </select>
               </td>
               <td>
                 <select v-model="field.required">
                   <option :value="true">
-                    <translate translate-context="*/*/*">Yes</translate>
+                    <translate translate-context="*/*/*">
+                      Yes
+                    </translate>
                   </option>
                   <option :value="false">
-                    <translate translate-context="*/*/*">No</translate>
+                    <translate translate-context="*/*/*">
+                      No
+                    </translate>
                   </option>
                 </select>
               </td>
               <td>
                 <i
                   :disabled="idx === 0"
-                  @click="move(idx, -1)" role="button"
+                  role="button"
                   :title="labels.up"
-                  :class="['up', 'arrow', {disabled: idx === 0}, 'icon']"></i>
+                  :class="['up', 'arrow', {disabled: idx === 0}, 'icon']"
+                  @click="move(idx, -1)"
+                />
                 <i
                   :disabled="idx >= local.fields.length - 1"
-                  @click="move(idx, 1)" role="button"
+                  role="button"
                   :title="labels.down"
-                  :class="['down', 'arrow', {disabled: idx >= local.fields.length - 1}, 'icon']"></i>
-                <i @click="remove(idx)" role="button" :title="labels.delete" class="x icon"></i>
+                  :class="['down', 'arrow', {disabled: idx >= local.fields.length - 1}, 'icon']"
+                  @click="move(idx, 1)"
+                />
+                <i
+                  role="button"
+                  :title="labels.delete"
+                  class="x icon"
+                  @click="remove(idx)"
+                />
               </td>
             </tr>
           </tbody>
         </table>
-        <div class="ui hidden divider"></div>
-        <button v-if="local.fields.length < maxFields" class="ui basic button" @click.stop.prevent="addField">
-          <translate translate-context="*/*/Form-builder">Add a new field</translate>
+        <div class="ui hidden divider" />
+        <button
+          v-if="local.fields.length < maxFields"
+          class="ui basic button"
+          @click.stop.prevent="addField"
+        >
+          <translate translate-context="*/*/Form-builder">
+            Add a new field
+          </translate>
         </button>
       </div>
     </div>
-    <div class="ui hidden divider"></div>
+    <div class="ui hidden divider" />
   </div>
 </template>
 
 <script>
 import lodash from '@/lodash'
 
-import SignupForm from "@/components/auth/SignupForm"
+import SignupForm from '@/components/auth/SignupForm'
 
-function arrayMove(arr, oldIndex, newIndex) {
+function arrayMove (arr, oldIndex, newIndex) {
   if (newIndex >= arr.length) {
-    var k = newIndex - arr.length + 1
+    let k = newIndex - arr.length + 1
     while (k--) {
       arr.push(undefined)
     }
@@ -122,40 +179,40 @@ function arrayMove(arr, oldIndex, newIndex) {
 // v-model with objects is complex, cf
 // https://simonkollross.de/posts/vuejs-using-v-model-with-objects-for-custom-components
 export default {
-  props: {
-    value: {type: Object},
-    signupApprovalEnabled: {type: Boolean},
-  },
   components: {
     SignupForm
   },
+  props: {
+    value: { type: Object, required: true },
+    signupApprovalEnabled: { type: Boolean }
+  },
   data () {
     return {
       maxFields: 10,
       isPreviewing: false
     }
   },
-  created () {
-    this.$emit('input', this.local)
-  },
   computed: {
     labels () {
       return {
         delete: this.$pgettext('*/*/*', 'Delete'),
         up: this.$pgettext('*/*/*', 'Move up'),
-        down: this.$pgettext('*/*/*', 'Move down'),
+        down: this.$pgettext('*/*/*', 'Move down')
       }
     },
-    local() {
-      return (this.value && this.value.fields) ? this.value : { help_text: {text: null, content_type: "text/markdown"}, fields: [] }
-    },
+    local () {
+      return (this.value && this.value.fields) ? this.value : { help_text: { text: null, content_type: 'text/markdown' }, fields: [] }
+    }
+  },
+  created () {
+    this.$emit('input', this.local)
   },
   methods: {
     addField () {
-      let newValue = lodash.tap(lodash.cloneDeep(this.local), v => v.fields.push({
+      const newValue = lodash.tap(lodash.cloneDeep(this.local), v => v.fields.push({
         label: this.$pgettext('*/*/Form-builder', 'Additional field') + ' ' + (this.local.fields.length + 1),
         required: true,
-        input_type: 'short_text',
+        input_type: 'short_text'
       }))
       this.$emit('input', newValue)
     },
@@ -169,10 +226,10 @@ export default {
       if (idx + incr >= this.local.fields.length) {
         return
       }
-      let newFields = arrayMove(lodash.cloneDeep(this.local).fields, idx, idx + incr)
+      const newFields = arrayMove(lodash.cloneDeep(this.local).fields, idx, idx + incr)
       this.update('fields', newFields)
     },
-    update(key, value) {
+    update (key, value) {
       if (key === 'help_text.text') {
         key = 'help_text'
         if (!value || value.length === 0) {
@@ -180,12 +237,12 @@ export default {
         } else {
           value = {
             text: value,
-            content_type: "text/markdown"
+            content_type: 'text/markdown'
           }
         }
       }
       this.$emit('input', lodash.tap(lodash.cloneDeep(this.local), v => lodash.set(v, key, value)))
-    },
-  },
+    }
+  }
 }
 </script>
diff --git a/front/src/components/audio/ArtistLabel.vue b/front/src/components/audio/ArtistLabel.vue
index 659d0884f..bbcc50293 100644
--- a/front/src/components/audio/ArtistLabel.vue
+++ b/front/src/components/audio/ArtistLabel.vue
@@ -1,25 +1,34 @@
 <template>
-  <router-link class="artist-label ui image label" :to="route">
-    <img alt="" :class="[{circular: artist.content_category != 'podcast'}]" v-if="artist.cover && artist.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](artist.cover.urls.medium_square_crop)" />
-    <i :class="[artist.content_category != 'podcast' ? 'circular' : 'bordered', 'inverted violet users icon']" v-else />
+  <router-link
+    class="artist-label ui image label"
+    :to="route"
+  >
+    <img
+      v-if="artist.cover && artist.cover.urls.original"
+      v-lazy="$store.getters['instance/absoluteUrl'](artist.cover.urls.medium_square_crop)"
+      alt=""
+      :class="[{circular: artist.content_category != 'podcast'}]"
+    >
+    <i
+      v-else
+      :class="[artist.content_category != 'podcast' ? 'circular' : 'bordered', 'inverted violet users icon']"
+    />
     {{ artist.name }}
   </router-link>
 </template>
 
 <script>
 
-import {momentFormat} from '@/filters'
-
 export default {
   props: {
-    artist: Object,
+    artist: { type: Object, required: true }
   },
   computed: {
     route () {
       if (this.artist.channel) {
-        return {name: 'channels.detail', params: {id: this.artist.channel.uuid}}
+        return { name: 'channels.detail', params: { id: this.artist.channel.uuid } }
       }
-      return {name: 'library.artists.detail', params: {id: this.artist.id}}
+      return { name: 'library.artists.detail', params: { id: this.artist.id } }
     }
   }
 }
diff --git a/front/src/components/audio/ChannelCard.vue b/front/src/components/audio/ChannelCard.vue
index 78cc14c44..8578e69ff 100644
--- a/front/src/components/audio/ChannelCard.vue
+++ b/front/src/components/audio/ChannelCard.vue
@@ -1,67 +1,100 @@
 <template>
   <div class="card app-card">
     <div
+      v-lazy:background-image="imageUrl"
+      :class="['ui', 'head-image', {'circular': object.artist.content_category != 'podcast'}, {'padded': object.artist.content_category === 'podcast'}, 'image', {'default-cover': !object.artist.cover}]"
       @click="$router.push({name: 'channels.detail', params: {id: urlId}})"
-      :class="['ui', 'head-image', {'circular': object.artist.content_category != 'podcast'}, {'padded': object.artist.content_category === 'podcast'}, 'image', {'default-cover': !object.artist.cover}]" v-lazy:background-image="imageUrl">
-      <play-button :icon-only="true" :is-playable="true" :button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']" :artist="object.artist"></play-button>
+    >
+      <play-button
+        :icon-only="true"
+        :is-playable="true"
+        :button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']"
+        :artist="object.artist"
+      />
     </div>
     <div class="content">
       <strong>
-        <router-link class="discrete link" :to="{name: 'channels.detail', params: {id: urlId}}">
+        <router-link
+          class="discrete link"
+          :to="{name: 'channels.detail', params: {id: urlId}}"
+        >
           {{ object.artist.name }}
         </router-link>
       </strong>
       <div class="description">
-        <translate class="meta ellipsis" translate-context="Content/Channel/Paragraph"
-          key="1"
+        <translate
           v-if="object.artist.content_category === 'podcast'"
+          key="1"
+          class="meta ellipsis"
+          translate-context="Content/Channel/Paragraph"
           translate-plural="%{ count } episodes"
           :translate-n="object.artist.tracks_count"
-          :translate-params="{count: object.artist.tracks_count}">
+          :translate-params="{count: object.artist.tracks_count}"
+        >
           %{ count } episode
         </translate>
-        <translate key="2" v-else translate-context="*/*/*" :translate-params="{count: object.artist.tracks_count}" :translate-n="object.artist.tracks_count" translate-plural="%{ count } tracks">%{ count } track</translate>
-        <tags-list label-classes="tiny" :truncate-size="20" :limit="2" :show-more="false" :tags="object.artist.tags"></tags-list>
+        <translate
+          v-else
+          key="2"
+          translate-context="*/*/*"
+          :translate-params="{count: object.artist.tracks_count}"
+          :translate-n="object.artist.tracks_count"
+          translate-plural="%{ count } tracks"
+        >
+          %{ count } track
+        </translate>
+        <tags-list
+          label-classes="tiny"
+          :truncate-size="20"
+          :limit="2"
+          :show-more="false"
+          :tags="object.artist.tags"
+        />
       </div>
-
     </div>
     <div class="extra content">
       <time
         v-translate
         class="meta ellipsis"
         :datetime="object.artist.modification_date"
-        :title="updatedTitle">
+        :title="updatedTitle"
+      >
         %{ updatedAgo }
       </time>
       <play-button
         class="right floated basic icon"
         :dropdown-only="true"
         :is-playable="true"
-        :dropdown-icon-classes="['ellipsis', 'horizontal', 'large really discrete']" :artist="object.artist" :channel="object" :account="object.attributed_to"></play-button>
+        :dropdown-icon-classes="['ellipsis', 'horizontal', 'large really discrete']"
+        :artist="object.artist"
+        :channel="object"
+        :account="object.attributed_to"
+      />
     </div>
   </div>
 </template>
 
 <script>
 import PlayButton from '@/components/audio/PlayButton'
-import TagsList from "@/components/tags/List"
+import TagsList from '@/components/tags/List'
 
-import {momentFormat} from '@/filters'
-import moment from "moment"
+import { momentFormat } from '@/filters'
+import moment from 'moment'
 
 export default {
-  props: {
-    object: {type: Object},
-  },
   components: {
     PlayButton,
     TagsList
   },
+  props: {
+    object: { type: Object, required: true }
+  },
   computed: {
     imageUrl () {
       if (this.object.artist.cover) {
         return this.$store.getters['instance/absoluteUrl'](this.object.artist.cover.urls.medium_square_crop)
       }
+      return null
     },
     urlId () {
       if (this.object.actor && this.object.actor.is_local) {
@@ -73,9 +106,9 @@ export default {
       }
     },
     updatedTitle () {
-      let d = momentFormat(this.object.artist.modification_date)
-      let message = this.$pgettext('*/*/*', 'Updated on %{ date }')
-      return this.$gettextInterpolate(message, {date: d})
+      const d = momentFormat(this.object.artist.modification_date)
+      const message = this.$pgettext('*/*/*', 'Updated on %{ date }')
+      return this.$gettextInterpolate(message, { date: d })
     },
     updatedAgo () {
       return moment(this.object.artist.modification_date).fromNow()
diff --git a/front/src/components/audio/ChannelEntries.vue b/front/src/components/audio/ChannelEntries.vue
index f067b5a75..21c4dc1c8 100644
--- a/front/src/components/audio/ChannelEntries.vue
+++ b/front/src/components/audio/ChannelEntries.vue
@@ -1,9 +1,12 @@
 <template>
   <div>
-    <slot></slot>
-    <div class="ui hidden divider"></div>
-    <div v-if="isLoading" class="ui inverted active dimmer">
-      <div class="ui loader"></div>
+    <slot />
+    <div class="ui hidden divider" />
+    <div
+      v-if="isLoading"
+      class="ui inverted active dimmer"
+    >
+      <div class="ui loader" />
     </div>
     <podcast-table
       v-if="isPodcast"
@@ -16,9 +19,10 @@
       :show-album="false"
       :paginate-results="true"
       :total="count"
-      @page-changed="updatePage"
       :page="page"
-      :paginate-by="limit"></podcast-table>
+      :paginate-by="limit"
+      @page-changed="updatePage"
+    />
     <track-table
       v-else
       :default-cover="defaultCover"
@@ -30,13 +34,19 @@
       :show-album="false"
       :paginate-results="true"
       :total="count"
-      @page-changed="updatePage"
       :page="page"
-      :paginate-by="limit"></track-table>
+      :paginate-by="limit"
+      @page-changed="updatePage"
+    />
     <template v-if="!isLoading && objects.length === 0">
-      <empty-state @refresh="fetchData('tracks/')" :refresh="true">
+      <empty-state
+        :refresh="true"
+        @refresh="fetchData('tracks/')"
+      >
         <p>
-          <translate translate-context="Content/Channels/*">You may need to subscribe to this channel to see its content.</translate>
+          <translate translate-context="Content/Channels/*">
+            You may need to subscribe to this channel to see its content.
+          </translate>
         </p>
       </empty-state>
     </template>
@@ -50,26 +60,31 @@ import PodcastTable from '@/components/audio/podcast/Table'
 import TrackTable from '@/components/audio/track/Table'
 
 export default {
-  props: {
-    filters: {type: Object, required: true},
-    limit: {type: Number, default: 10},
-    defaultCover: {type: Object},
-    isPodcast: {type: Boolean, required: true},
-  },
   components: {
     PodcastTable,
-    TrackTable,
+    TrackTable
+  },
+  props: {
+    filters: { type: Object, required: true },
+    limit: { type: Number, default: 10 },
+    defaultCover: { type: Object, required: true },
+    isPodcast: { type: Boolean, required: true }
   },
   data () {
     return {
       objects: [],
       count: 0,
       isLoading: false,
-      errors: null,
+      errors: [],
       nextPage: null,
       page: 1
     }
   },
+  watch: {
+    page () {
+      this.fetchData('tracks/')
+    }
+  },
   created () {
     this.fetchData('tracks/')
   },
@@ -79,31 +94,26 @@ export default {
         return
       }
       this.isLoading = true
-      let self = this
-      let params = _.clone(this.filters)
+      const self = this
+      const params = _.clone(this.filters)
       params.page_size = this.limit
       params.page = this.page
       params.include_channels = true
       try {
-      let channelsPromise = await axios.get(url, {params: params})
-      self.nextPage = channelsPromise.data.next
-      self.objects = channelsPromise.data.results
-      self.count = channelsPromise.data.count
-      self.$emit('fetched', channelsPromise.data)
-      self.isLoading = false
-      } catch(e) {
+        const channelsPromise = await axios.get(url, { params: params })
+        self.nextPage = channelsPromise.data.next
+        self.objects = channelsPromise.data.results
+        self.count = channelsPromise.data.count
+        self.$emit('fetched', channelsPromise.data)
+        self.isLoading = false
+      } catch (e) {
         self.isLoading = false
-        self.errors = error.backendErrors
+        self.errors = e.backendErrors
       }
     },
-    updatePage: function(page) {
+    updatePage: function (page) {
       this.page = page
     }
-  },
-  watch: {
-    page() {
-      this.fetchData('tracks/')
-    }
   }
 }
 </script>
diff --git a/front/src/components/audio/ChannelEntryCard.vue b/front/src/components/audio/ChannelEntryCard.vue
index 79ca6baf0..2ca1f93e0 100644
--- a/front/src/components/audio/ChannelEntryCard.vue
+++ b/front/src/components/audio/ChannelEntryCard.vue
@@ -1,48 +1,77 @@
 <template>
   <div :class="[{active: currentTrack && isPlaying && entry.id === currentTrack.id}, 'channel-entry-card']">
     <div class="controls">
-      <play-button class="basic circular icon" :discrete="true" :icon-only="true" :is-playable="true" :button-classes="['ui', 'circular', 'inverted vibrant', 'icon', 'button']" :track="entry"></play-button>
+      <play-button
+        class="basic circular icon"
+        :discrete="true"
+        :icon-only="true"
+        :is-playable="true"
+        :button-classes="['ui', 'circular', 'inverted vibrant', 'icon', 'button']"
+        :track="entry"
+      />
     </div>
     <img
-      @click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})"
+      v-if="cover && cover.urls.original"
+      v-lazy="$store.getters['instance/absoluteUrl'](cover.urls.medium_square_crop)"
       alt=""
       class="channel-image image"
-      v-if="cover && cover.urls.original"
-      v-lazy="$store.getters['instance/absoluteUrl'](cover.urls.medium_square_crop)">
-    <img
       @click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})"
-      class="channel-image image"
-      v-else-if="entry.artist.content_category === 'podcast' && defaultCover != undefined"
-      v-lazy="$store.getters['instance/absoluteUrl'](defaultCover.urls.medium_square_crop)">
+    >
     <img
+      v-else-if="entry.artist.content_category === 'podcast' && defaultCover != undefined"
+      v-lazy="$store.getters['instance/absoluteUrl'](defaultCover.urls.medium_square_crop)"
+      class="channel-image image"
       @click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})"
+    >
+    <img
+      v-else-if="entry.album && entry.album.cover && entry.album.cover.urls.original"
+      v-lazy="$store.getters['instance/absoluteUrl'](entry.album.cover.urls.medium_square_crop)"
       alt=""
       class="channel-image image"
-      v-else-if="entry.album && entry.album.cover && entry.album.cover.urls.original"
-      v-lazy="$store.getters['instance/absoluteUrl'](entry.album.cover.urls.medium_square_crop)">
-    <img
       @click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})"
+    >
+    <img
+      v-else
       alt=""
       class="channel-image image"
-      v-else
-      src="../../assets/audio/default-cover.png">
+      src="../../assets/audio/default-cover.png"
+      @click="$router.push({name: 'library.tracks.detail', params: {id: entry.id}})"
+    >
     <div class="ellipsis content">
       <strong>
-        <router-link class="discrete link" :to="{name: 'library.tracks.detail', params: {id: entry.id}}">
+        <router-link
+          class="discrete link"
+          :to="{name: 'library.tracks.detail', params: {id: entry.id}}"
+        >
           {{ entry.title }}
         </router-link>
       </strong>
       <br>
-      <human-date class="really discrete" :date="entry.creation_date"></human-date>
+      <human-date
+        class="really discrete"
+        :date="entry.creation_date"
+      />
     </div>
     <div class="meta">
-        <template v-if="$store.state.auth.authenticated && $store.getters['favorites/isFavorite'](entry.id)">
-          <track-favorite-icon class="tiny" :track="entry"></track-favorite-icon>
-        </template>
-        <human-duration v-if="duration" :duration="duration"></human-duration>
+      <template v-if="$store.state.auth.authenticated && $store.getters['favorites/isFavorite'](entry.id)">
+        <track-favorite-icon
+          class="tiny"
+          :track="entry"
+        />
+      </template>
+      <human-duration
+        v-if="duration"
+        :duration="duration"
+      />
     </div>
     <div class="controls">
-      <play-button class="play-button basic icon" :dropdown-only="true" :is-playable="entry.is_playable" :dropdown-icon-classes="['ellipsis', 'vertical', 'large really discrete']" :track="entry"></play-button>      
+      <play-button
+        class="play-button basic icon"
+        :dropdown-only="true"
+        :is-playable="entry.is_playable"
+        :dropdown-icon-classes="['ellipsis', 'vertical', 'large really discrete']"
+        :track="entry"
+      />
     </div>
   </div>
 </template>
@@ -50,19 +79,21 @@
 <script>
 import PlayButton from '@/components/audio/PlayButton'
 import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
-import { mapGetters } from "vuex"
-
+import { mapGetters } from 'vuex'
 
 export default {
-  props: ['entry', 'defaultCover'],
   components: {
     PlayButton,
-    TrackFavoriteIcon,
+    TrackFavoriteIcon
+  },
+  props: {
+    entry: { type: Object, required: true },
+    defaultCover: { type: Object, required: true }
   },
   computed: {
 
     ...mapGetters({
-      currentTrack: "queue/currentTrack",
+      currentTrack: 'queue/currentTrack'
     }),
 
     isPlaying () {
@@ -72,14 +103,16 @@ export default {
       if (this.entry.cover) {
         return this.entry.cover
       }
+      return null
     },
     duration () {
-      let uploads = this.entry.uploads.filter((e) => {
+      const uploads = this.entry.uploads.filter((e) => {
         return e.duration
       })
       if (uploads.length > 0) {
         return uploads[0].duration
       }
+      return null
     }
   }
 }
diff --git a/front/src/components/audio/ChannelForm.vue b/front/src/components/audio/ChannelForm.vue
index fbdb8280a..3c96a9319 100644
--- a/front/src/components/audio/ChannelForm.vue
+++ b/front/src/components/audio/ChannelForm.vue
@@ -1,24 +1,55 @@
 <template>
-  <form class="ui form" @submit.prevent.stop="submit">
-    <div v-if="errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/*/Error message.Title">Error while saving channel</translate></h4>
+  <form
+    class="ui form"
+    @submit.prevent.stop="submit"
+  >
+    <div
+      v-if="errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/*/Error message.Title">
+          Error while saving channel
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
     <template v-if="metadataChoices">
-      <fieldset v-if="creating && step === 1" class="ui grouped channel-type required field">
+      <fieldset
+        v-if="creating && step === 1"
+        class="ui grouped channel-type required field"
+      >
         <legend>
-          <translate translate-context="Content/Channel/Paragraph">What will this channel be used for?</translate>
+          <translate translate-context="Content/Channel/Paragraph">
+            What will this channel be used for?
+          </translate>
         </legend>
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
         <div class="field">
-          <div :class="['ui', 'radio', 'checkbox', {selected: choice.value == newValues.content_category}]" v-for="choice in categoryChoices">
-            <input type="radio" name="channel-category" :id="`category-${choice.value}`" :value="choice.value" v-model="newValues.content_category">
+          <div
+            v-for="(choice, key) in categoryChoices"
+            :key="key"
+            :class="['ui', 'radio', 'checkbox', {selected: choice.value == newValues.content_category}]"
+          >
+            <input
+              :id="`category-${choice.value}`"
+              v-model="newValues.content_category"
+              type="radio"
+              name="channel-category"
+              :value="choice.value"
+            >
             <label :for="`category-${choice.value}`">
-              <span :class="['right floated', 'placeholder', 'image', {circular: choice.value === 'music'}]"></span>
+              <span :class="['right floated', 'placeholder', 'image', {circular: choice.value === 'music'}]" />
               <strong>{{ choice.label }}</strong>
-              <div class="ui small hidden divider"></div>
+              <div class="ui small hidden divider" />
               {{ choice.helpText }}
             </label>
           </div>
@@ -29,20 +60,35 @@
           <label for="channel-name">
             <translate translate-context="Content/Channel/*">Name</translate>
           </label>
-          <input type="text" required v-model="newValues.name" :placeholder="labels.namePlaceholder">
+          <input
+            v-model="newValues.name"
+            type="text"
+            required
+            :placeholder="labels.namePlaceholder"
+          >
         </div>
         <div class="ui required field">
           <label for="channel-username">
             <translate translate-context="Content/Channel/*">Fediverse handle</translate>
           </label>
           <div class="ui left labeled input">
-            <div class="ui basic label">@</div>
-            <input type="text" :required="creating" :disabled="!creating" :placeholder="labels.usernamePlaceholder" v-model="newValues.username">
+            <div class="ui basic label">
+              @
+            </div>
+            <input
+              v-model="newValues.username"
+              type="text"
+              :required="creating"
+              :disabled="!creating"
+              :placeholder="labels.usernamePlaceholder"
+            >
           </div>
           <template v-if="creating">
-            <div class="ui small hidden divider"></div>
+            <div class="ui small hidden divider" />
             <p>
-              <translate translate-context="Content/Channels/Paragraph">Used in URLs and to follow this channel in the Fediverse. It cannot be changed later.</translate>
+              <translate translate-context="Content/Channels/Paragraph">
+                Used in URLs and to follow this channel in the Fediverse. It cannot be changed later.
+              </translate>
             </p>
           </template>
         </div>
@@ -51,12 +97,17 @@
             v-model="newValues.cover"
             :required="false"
             :image-class="newValues.content_category === 'podcast' ? '' : 'circular'"
-            @delete="newValues.cover = null">
-            <translate translate-context="Content/Channel/*" slot="label">Channel Picture</translate>
+            @delete="newValues.cover = null"
+          >
+            <translate
+              slot="label"
+              translate-context="Content/Channel/*"
+            >
+              Channel Picture
+            </translate>
           </attachment-input>
-
         </div>
-        <div class="ui small hidden divider"></div>
+        <div class="ui small hidden divider" />
         <div class="ui stackable grid row">
           <div class="ten wide column">
             <div class="ui field">
@@ -64,46 +115,67 @@
                 <translate translate-context="*/*/*">Tags</translate>
               </label>
               <tags-selector
-                v-model="newValues.tags"
                 id="channel-tags"
-                :required="false"></tags-selector>
+                v-model="newValues.tags"
+                :required="false"
+              />
             </div>
           </div>
-          <div class="six wide column" v-if="newValues.content_category === 'podcast'">
+          <div
+            v-if="newValues.content_category === 'podcast'"
+            class="six wide column"
+          >
             <div class="ui required field">
               <label for="channel-language">
                 <translate translate-context="*/*/*">Language</translate>
               </label>
               <select
-                name="channel-language"
                 id="channel-language"
                 v-model="newValues.metadata.language"
+                name="channel-language"
                 required
-                class="ui search selection dropdown">
-                <option v-for="v in metadataChoices.language" :value="v.value">{{ v.label }}</option>
+                class="ui search selection dropdown"
+              >
+                <option
+                  v-for="(v, key) in metadataChoices.language"
+                  :key="key"
+                  :value="v.value"
+                >
+                  {{ v.label }}
+                </option>
               </select>
             </div>
           </div>
         </div>
-        <div class="ui small hidden divider"></div>
+        <div class="ui small hidden divider" />
         <div class="ui field">
           <label for="channel-name">
             <translate translate-context="*/*/*">Description</translate>
           </label>
-          <content-form v-model="newValues.description"></content-form>
+          <content-form v-model="newValues.description" />
         </div>
-        <div class="ui two fields" v-if="newValues.content_category === 'podcast'">
+        <div
+          v-if="newValues.content_category === 'podcast'"
+          class="ui two fields"
+        >
           <div class="ui required field">
             <label for="channel-itunes-category">
               <translate translate-context="*/*/*">Category</translate>
             </label>
             <select
-              name="itunes-category"
               id="itunes-category"
               v-model="newValues.metadata.itunes_category"
+              name="itunes-category"
               required
-              class="ui dropdown">
-              <option v-for="v in metadataChoices.itunes_category" :value="v.value">{{ v.label }}</option>
+              class="ui dropdown"
+            >
+              <option
+                v-for="(v, key) in metadataChoices.itunes_category"
+                :key="key"
+                :value="v.value"
+              >
+                {{ v.label }}
+              </option>
             </select>
           </div>
           <div class="ui field">
@@ -111,45 +183,64 @@
               <translate translate-context="*/*/*">Subcategory</translate>
             </label>
             <select
-              name="itunes-category"
               id="itunes-category"
               v-model="newValues.metadata.itunes_subcategory"
+              name="itunes-category"
               :disabled="!newValues.metadata.itunes_category"
-              class="ui dropdown">
-              <option v-for="v in itunesSubcategories" :value="v">{{ v }}</option>
+              class="ui dropdown"
+            >
+              <option
+                v-for="(v, key) in itunesSubcategories"
+                :key="key"
+                :value="v"
+              >
+                {{ v }}
+              </option>
             </select>
           </div>
         </div>
-        <div class="ui two fields" v-if="newValues.content_category === 'podcast'">
+        <div
+          v-if="newValues.content_category === 'podcast'"
+          class="ui two fields"
+        >
           <div class="ui field">
             <label for="channel-itunes-email">
               <translate translate-context="*/*/*">Owner e-mail address</translate>
             </label>
             <input
-              name="channel-itunes-email"
               id="channel-itunes-email"
+              v-model="newValues.metadata.owner_email"
+              name="channel-itunes-email"
               type="email"
-              v-model="newValues.metadata.owner_email">
+            >
           </div>
           <div class="ui field">
             <label for="channel-itunes-name">
               <translate translate-context="*/*/*">Owner name</translate>
             </label>
             <input
-              name="channel-itunes-name"
               id="channel-itunes-name"
+              v-model="newValues.metadata.owner_name"
+              name="channel-itunes-name"
               maxlength="255"
-              v-model="newValues.metadata.owner_name">
+            >
           </div>
         </div>
         <p>
-          <translate translate-context="*/*/*">Used for the itunes:email and itunes:name field required by certain platforms such as Spotify or iTunes.</translate>
+          <translate translate-context="*/*/*">
+            Used for the itunes:email and itunes:name field required by certain platforms such as Spotify or iTunes.
+          </translate>
         </p>
       </template>
     </template>
-    <div v-else class="ui active inverted dimmer">
+    <div
+      v-else
+      class="ui active inverted dimmer"
+    >
       <div class="ui text loader">
-        <translate translate-context="*/*/*">Loading</translate>
+        <translate translate-context="*/*/*">
+          Loading
+        </translate>
       </div>
     </div>
   </form>
@@ -161,29 +252,25 @@ import axios from 'axios'
 import AttachmentInput from '@/components/common/AttachmentInput'
 import TagsSelector from '@/components/library/TagsSelector'
 
-function slugify(text) {
+function slugify (text) {
   return text.toString().toLowerCase()
-    .replace(/\s+/g, '')           // Remove spaces
-    .replace(/[^\w]+/g, '')        // Remove all non-word chars
+    .replace(/\s+/g, '') // Remove spaces
+    .replace(/[^\w]+/g, '') // Remove all non-word chars
 }
 
 export default {
-  props: {
-    object: {type: Object, required: false, default: null},
-    step: {type: Number, required: false, default: 1},
-  },
   components: {
     AttachmentInput,
     TagsSelector
   },
-
-  created () {
-    this.fetchMetadataChoices()
+  props: {
+    object: { type: Object, required: false, default: null },
+    step: { type: Number, required: false, default: 1 }
   },
   data () {
-    let oldValues = {}
+    const oldValues = {}
     if (this.object) {
-      oldValues.metadata = {...(this.object.metadata || {})}
+      oldValues.metadata = { ...(this.object.metadata || {}) }
       oldValues.name = this.object.artist.name
       oldValues.description = this.object.artist.description
       oldValues.cover = this.object.artist.cover
@@ -196,13 +283,13 @@ export default {
       errors: [],
       metadataChoices: null,
       newValues: {
-        name: oldValues.name || "",
-        username: oldValues.username || "",
+        name: oldValues.name || '',
+        username: oldValues.username || '',
         tags: oldValues.tags || [],
-        description: (oldValues.description || {}).text || "",
+        description: (oldValues.description || {}).text || '',
         cover: (oldValues.cover || {}).uuid || null,
-        content_category: oldValues.content_category || "podcast",
-        metadata: oldValues.metadata || {},
+        content_category: oldValues.content_category || 'podcast',
+        metadata: oldValues.metadata || {}
       }
     }
   },
@@ -213,20 +300,20 @@ export default {
     categoryChoices () {
       return [
         {
-          value: "podcast",
-          label: this.$pgettext('*/*/*', "Podcasts"),
-          helpText: this.$pgettext('Content/Channels/Help', "Host your episodes and keep your community updated."),
+          value: 'podcast',
+          label: this.$pgettext('*/*/*', 'Podcasts'),
+          helpText: this.$pgettext('Content/Channels/Help', 'Host your episodes and keep your community updated.')
         },
         {
-          value: "music",
-          label: this.$pgettext('*/*/*', "Artist discography"),
-          helpText: this.$pgettext('Content/Channels/Help', "Publish music you make as a nice discography of albums and singles."),
+          value: 'music',
+          label: this.$pgettext('*/*/*', 'Artist discography'),
+          helpText: this.$pgettext('Content/Channels/Help', 'Publish music you make as a nice discography of albums and singles.')
         }
       ]
     },
     itunesSubcategories () {
       for (let index = 0; index < this.metadataChoices.itunes_category.length; index++) {
-        const element = this.metadataChoices.itunes_category[index];
+        const element = this.metadataChoices.itunes_category[index]
         if (element.value === this.newValues.metadata.itunes_category) {
           return element.children || []
         }
@@ -235,8 +322,8 @@ export default {
     },
     labels () {
       return {
-        namePlaceholder: this.$pgettext('Content/Channel/Form.Field.Placeholder', "Awesome channel name"),
-        usernamePlaceholder: this.$pgettext('Content/Channel/Form.Field.Placeholder', "awesomechannelname"),
+        namePlaceholder: this.$pgettext('Content/Channel/Form.Field.Placeholder', 'Awesome channel name'),
+        usernamePlaceholder: this.$pgettext('Content/Channel/Form.Field.Placeholder', 'awesomechannelname')
       }
     },
     submittable () {
@@ -247,9 +334,41 @@ export default {
       return !!v
     }
   },
+  watch: {
+    'newValues.name' (v) {
+      if (this.creating) {
+        this.newValues.username = slugify(v)
+      }
+    },
+    'newValues.metadata.itunes_category' (v) {
+      this.newValues.metadata.itunes_subcategory = null
+    },
+    'newValues.content_category': {
+      handler (v) {
+        this.$emit('category', v)
+      },
+      immediate: true
+    },
+    isLoading: {
+      handler (v) {
+        this.$emit('loading', v)
+      },
+      immediate: true
+    },
+    submittable: {
+      handler (v) {
+        this.$emit('submittable', v)
+      },
+      immediate: true
+    }
+  },
+
+  created () {
+    this.fetchMetadataChoices()
+  },
   methods: {
     fetchMetadataChoices () {
-      let self = this
+      const self = this
       axios.get('channels/metadata-choices').then((response) => {
         self.metadataChoices = response.data
       }, error => {
@@ -258,21 +377,21 @@ export default {
     },
     submit () {
       this.isLoading = true
-      let self = this
-      let handler = this.creating ? axios.post : axios.patch
-      let url = this.creating ? `channels/` : `channels/${this.object.uuid}`
-      let payload = {
+      const self = this
+      const handler = this.creating ? axios.post : axios.patch
+      const url = this.creating ? 'channels/' : `channels/${this.object.uuid}`
+      const payload = {
         name: this.newValues.name,
         username: this.newValues.username,
         tags: this.newValues.tags,
         content_category: this.newValues.content_category,
         cover: this.newValues.cover,
-        metadata: this.newValues.metadata,
+        metadata: this.newValues.metadata
       }
       if (this.newValues.description) {
         payload.description = {
           content_type: 'text/markdown',
-          text: this.newValues.description,
+          text: this.newValues.description
         }
       } else {
         payload.description = null
@@ -291,34 +410,6 @@ export default {
         self.$emit('errored', self.errors)
       })
     }
-  },
-  watch: {
-    "newValues.name" (v) {
-      if (this.creating) {
-        this.newValues.username = slugify(v)
-      }
-    },
-    "newValues.metadata.itunes_category" (v) {
-      this.newValues.metadata.itunes_subcategory = null
-    },
-    "newValues.content_category": {
-      handler (v) {
-        this.$emit("category", v)
-      },
-      immediate: true
-    },
-    isLoading: {
-      handler (v) {
-        this.$emit("loading", v)
-      },
-      immediate: true
-    },
-    submittable: {
-      handler (v) {
-        this.$emit("submittable", v)
-      },
-      immediate: true
-    },
   }
 }
 </script>
diff --git a/front/src/components/audio/ChannelSerieCard.vue b/front/src/components/audio/ChannelSerieCard.vue
index 84f737c52..ab5542f75 100644
--- a/front/src/components/audio/ChannelSerieCard.vue
+++ b/front/src/components/audio/ChannelSerieCard.vue
@@ -1,28 +1,62 @@
 <template>
   <div class="channel-serie-card">
     <div class="two-images">
-      <img alt="" @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})" class="channel-image" v-if="cover && cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](cover.urls.medium_square_crop)">
-      <img alt="" @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})" class="channel-image" v-else src="../../assets/audio/default-cover.png">
-      <img alt="" @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})" class="channel-image" v-if="cover && cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](cover.urls.medium_square_crop)">
-      <img alt="" @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})" class="channel-image" v-else src="../../assets/audio/default-cover.png">
+      <img
+        v-if="cover && cover.urls.original"
+        v-lazy="$store.getters['instance/absoluteUrl'](cover.urls.medium_square_crop)"
+        alt=""
+        class="channel-image"
+        @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})"
+      >
+      <img
+        v-else
+        alt=""
+        class="channel-image"
+        src="../../assets/audio/default-cover.png"
+        @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})"
+      >
+      <img
+        v-if="cover && cover.urls.original"
+        v-lazy="$store.getters['instance/absoluteUrl'](cover.urls.medium_square_crop)"
+        alt=""
+        class="channel-image"
+        @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})"
+      >
+      <img
+        v-else
+        alt=""
+        class="channel-image"
+        src="../../assets/audio/default-cover.png"
+        @click="$router.push({name: 'library.albums.detail', params: {id: serie.id}})"
+      >
     </div>
     <div class="content ellipsis">
       <strong>
-        <router-link class="discrete link" :to="{name: 'library.albums.detail', params: {id: serie.id}}">
+        <router-link
+          class="discrete link"
+          :to="{name: 'library.albums.detail', params: {id: serie.id}}"
+        >
           {{ serie.title }}
         </router-link>
       </strong>
       <div class="description">
-        <translate translate-context="Content/Channel/Paragraph"
+        <translate
+          translate-context="Content/Channel/Paragraph"
           translate-plural="%{ count } episodes"
           :translate-n="serie.tracks_count"
-          :translate-params="{count: serie.tracks_count}">
+          :translate-params="{count: serie.tracks_count}"
+        >
           %{ count } episode
         </translate>
       </div>
     </div>
     <div class="controls">
-      <play-button :icon-only="true" :is-playable="true" :button-classes="['ui', 'circular', 'vibrant', 'icon', 'button']" :album="serie"></play-button>
+      <play-button
+        :icon-only="true"
+        :is-playable="true"
+        :button-classes="['ui', 'circular', 'vibrant', 'icon', 'button']"
+        :album="serie"
+      />
     </div>
   </div>
 </template>
@@ -31,18 +65,19 @@
 import PlayButton from '@/components/audio/PlayButton'
 
 export default {
-  props: ['serie'],
   components: {
-    PlayButton,
+    PlayButton
   },
+  props: { serie: { type: Object, required: true } },
   computed: {
     cover () {
       if (this.serie.cover) {
         return this.serie.cover
       }
+      return null
     },
     duration () {
-      let uploads = this.serie.uploads.filter((e) => {
+      const uploads = this.serie.uploads.filter((e) => {
         return e.duration
       })
       return uploads[0].duration
diff --git a/front/src/components/audio/ChannelSeries.vue b/front/src/components/audio/ChannelSeries.vue
index 69e735987..26b0aa0be 100644
--- a/front/src/components/audio/ChannelSeries.vue
+++ b/front/src/components/audio/ChannelSeries.vue
@@ -1,26 +1,51 @@
 <template>
   <div>
-    <slot></slot>
-    <div class="ui hidden divider"></div>
-    <div v-if="isLoading" class="ui inverted active dimmer">
-      <div class="ui loader"></div>
+    <slot />
+    <div class="ui hidden divider" />
+    <div
+      v-if="isLoading"
+      class="ui inverted active dimmer"
+    >
+      <div class="ui loader" />
     </div>
     <template v-if="isPodcast">
-      <channel-serie-card v-for="serie in objects" :serie="serie" :key="serie.id" />
+      <channel-serie-card
+        v-for="serie in objects"
+        :key="serie.id"
+        :serie="serie"
+      />
     </template>
-    <div v-else class="ui app-cards cards">
-      <album-card v-for="album in objects" :album="album" :key="album.id" />
+    <div
+      v-else
+      class="ui app-cards cards"
+    >
+      <album-card
+        v-for="album in objects"
+        :key="album.id"
+        :album="album"
+      />
     </div>
     <template v-if="nextPage">
-      <div class="ui hidden divider"></div>
-      <button v-if="nextPage" @click="fetchData(nextPage)" :class="['ui', 'basic', 'button']">
-        <translate translate-context="*/*/Button,Label">Show more</translate>
+      <div class="ui hidden divider" />
+      <button
+        v-if="nextPage"
+        :class="['ui', 'basic', 'button']"
+        @click="fetchData(nextPage)"
+      >
+        <translate translate-context="*/*/Button,Label">
+          Show more
+        </translate>
       </button>
     </template>
     <template v-if="!isLoading && objects.length === 0">
-      <empty-state @refresh="fetchData('albums/')" :refresh="true">
+      <empty-state
+        :refresh="true"
+        @refresh="fetchData('albums/')"
+      >
         <p>
-          <translate translate-context="Content/Channels/*">You may need to subscribe to this channel to see its contents.</translate>
+          <translate translate-context="Content/Channels/*">
+            You may need to subscribe to this channel to see its contents.
+          </translate>
         </p>
       </empty-state>
     </template>
@@ -33,16 +58,15 @@ import axios from 'axios'
 import ChannelSerieCard from '@/components/audio/ChannelSerieCard'
 import AlbumCard from '@/components/audio/album/Card'
 
-
 export default {
-  props: {
-    filters: {type: Object, required: true},
-    isPodcast: {type: Boolean, default: true},
-    limit: {type: Number, default: 5},
-  },
   components: {
     ChannelSerieCard,
-    AlbumCard,
+    AlbumCard
+  },
+  props: {
+    filters: { type: Object, required: true },
+    isPodcast: { type: Boolean, default: true },
+    limit: { type: Number, default: 5 }
   },
   data () {
     return {
@@ -62,11 +86,11 @@ export default {
         return
       }
       this.isLoading = true
-      let self = this
-      let params = _.clone(this.filters)
+      const self = this
+      const params = _.clone(this.filters)
       params.page_size = this.limit
       params.include_channels = true
-      axios.get(url, {params: params}).then((response) => {
+      axios.get(url, { params: params }).then((response) => {
         self.nextPage = response.data.next
         self.isLoading = false
         self.objects = self.objects.concat(response.data.results)
@@ -75,7 +99,7 @@ export default {
         self.isLoading = false
         self.errors = error.backendErrors
       })
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/audio/ChannelsWidget.vue b/front/src/components/audio/ChannelsWidget.vue
index f6957a4b6..68b26feaa 100644
--- a/front/src/components/audio/ChannelsWidget.vue
+++ b/front/src/components/audio/ChannelsWidget.vue
@@ -1,21 +1,37 @@
 <template>
   <div>
-    <slot></slot>
-    <div class="ui hidden divider"></div>
+    <slot />
+    <div class="ui hidden divider" />
     <div class="ui app-cards cards">
-      <div v-if="isLoading" class="ui inverted active dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui inverted active dimmer"
+      >
+        <div class="ui loader" />
       </div>
-      <channel-card v-for="object in objects" :object="object" :key="object.uuid" />
+      <channel-card
+        v-for="object in objects"
+        :key="object.uuid"
+        :object="object"
+      />
     </div>
     <template v-if="nextPage">
-      <div class="ui hidden divider"></div>
-      <button v-if="nextPage" @click="fetchData(nextPage)" :class="['ui', 'basic', 'button']">
-        <translate translate-context="*/*/Button,Label">Show more</translate>
+      <div class="ui hidden divider" />
+      <button
+        v-if="nextPage"
+        :class="['ui', 'basic', 'button']"
+        @click="fetchData(nextPage)"
+      >
+        <translate translate-context="*/*/Button,Label">
+          Show more
+        </translate>
       </button>
     </template>
     <template v-if="!isLoading && objects.length === 0">
-      <empty-state @refresh="fetchData('channels/')" :refresh="true"></empty-state>
+      <empty-state
+        :refresh="true"
+        @refresh="fetchData('channels/')"
+      />
     </template>
   </div>
 </template>
@@ -26,13 +42,13 @@ import axios from 'axios'
 import ChannelCard from '@/components/audio/ChannelCard'
 
 export default {
-  props: {
-    filters: {type: Object, required: true},
-    limit: {type: Number, default: 5},
-  },
   components: {
     ChannelCard
   },
+  props: {
+    filters: { type: Object, required: true },
+    limit: { type: Number, default: 5 }
+  },
   data () {
     return {
       objects: [],
@@ -51,11 +67,11 @@ export default {
         return
       }
       this.isLoading = true
-      let self = this
-      let params = _.clone(this.filters)
+      const self = this
+      const params = _.clone(this.filters)
       params.page_size = this.limit
       params.include_channels = true
-      axios.get(url, {params: params}).then((response) => {
+      axios.get(url, { params: params }).then((response) => {
         self.nextPage = response.data.next
         self.isLoading = false
         self.objects = self.objects.concat(response.data.results)
@@ -65,7 +81,7 @@ export default {
         self.isLoading = false
         self.errors = error.backendErrors
       })
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/audio/EmbedWizard.vue b/front/src/components/audio/EmbedWizard.vue
index 81103879d..2dc136afa 100644
--- a/front/src/components/audio/EmbedWizard.vue
+++ b/front/src/components/audio/EmbedWizard.vue
@@ -1,13 +1,19 @@
 <template>
   <div>
-    <div role="alert" class="ui warning message" v-if="!anonymousCanListen">
+    <div
+      v-if="!anonymousCanListen"
+      role="alert"
+      class="ui warning message"
+    >
       <p>
         <strong>
           <translate translate-context="Content/Embed/Message">Sharing will not work because this pod doesn't allow anonymous users to access content.</translate>
         </strong>
       </p>
       <p>
-        <translate translate-context="Content/Embed/Message">Please contact your admins and ask them to update the corresponding setting.</translate>
+        <translate translate-context="Content/Embed/Message">
+          Please contact your admins and ask them to update the corresponding setting.
+        </translate>
       </p>
     </div>
     <div class="ui form">
@@ -15,49 +21,100 @@
         <div class="field">
           <div class="field">
             <label for="embed-width"><translate translate-context="Popup/Embed/Input.Label">Widget width</translate></label>
-            <p><translate translate-context="Popup/Embed/Paragraph">Leave empty for a responsive widget</translate></p>
-            <input id="embed-width" type="number" v-model.number="width" min="0" step="10" />
+            <p>
+              <translate translate-context="Popup/Embed/Paragraph">
+                Leave empty for a responsive widget
+              </translate>
+            </p>
+            <input
+              id="embed-width"
+              v-model.number="width"
+              type="number"
+              min="0"
+              step="10"
+            >
           </div>
           <template v-if="type != 'track'">
             <br>
             <div class="field">
               <label for="embed-height"><translate translate-context="Popup/Embed/Input.Label">Widget height</translate></label>
-              <input id="embed-height" type="number" v-model="height" :min="minHeight" max="1000" step="10" />
+              <input
+                id="embed-height"
+                v-model="height"
+                type="number"
+                :min="minHeight"
+                max="1000"
+                step="10"
+              >
             </div>
           </template>
         </div>
         <div class="field">
-          <button @click="copy" class="ui right accent labeled icon floated button"><i class="copy icon"></i><translate translate-context="*/*/Button.Label/Short, Verb">Copy</translate></button>
+          <button
+            class="ui right accent labeled icon floated button"
+            @click="copy"
+          >
+            <i class="copy icon" /><translate translate-context="*/*/Button.Label/Short, Verb">
+              Copy
+            </translate>
+          </button>
           <label for="embed-width"><translate translate-context="Popup/Embed/Input.Label/Noun">Embed code</translate></label>
-          <p><translate translate-context="Popup/Embed/Paragraph">Copy/paste this code in your website HTML</translate></p>
-          <textarea ref="textarea" :value="embedCode" rows="5" readonly>
-          </textarea>
+          <p>
+            <translate translate-context="Popup/Embed/Paragraph">
+              Copy/paste this code in your website HTML
+            </translate>
+          </p>
+          <textarea
+            ref="textarea"
+            :value="embedCode"
+            rows="5"
+            readonly
+          />
           <div class="ui right">
-          <p class="message" v-if=copied><translate translate-context="Content/*/Paragraph">Text copied to clipboard!</translate></p>
+            <p
+              v-if="copied"
+              class="message"
+            >
+              <translate translate-context="Content/*/Paragraph">
+                Text copied to clipboard!
+              </translate>
+            </p>
           </div>
         </div>
       </div>
     </div>
     <div class="preview">
       <h3>
-        <a :href="iframeSrc" target="_blank">
+        <a
+          :href="iframeSrc"
+          target="_blank"
+        >
           <translate translate-context="Popup/Embed/Title/Noun">Preview</translate>
         </a>
       </h3>
-      <iframe :width="frameWidth" :height="height" scrolling="no" frameborder="no" :src="iframeSrc"></iframe>
+      <iframe
+        :width="frameWidth"
+        :height="height"
+        scrolling="no"
+        frameborder="no"
+        :src="iframeSrc"
+      />
     </div>
   </div>
 </template>
 
 <script>
 
-import { mapState } from "vuex"
+import { mapState } from 'vuex'
 import _ from '@/lodash'
 
 export default {
-  props: ['type', 'id'],
+  props: {
+    type: { type: String, required: true },
+    id: { type: Number, required: true }
+  },
   data () {
-    let d = {
+    const d = {
       width: null,
       height: 150,
       minHeight: 100,
@@ -71,7 +128,7 @@ export default {
   },
   computed: {
     ...mapState({
-      nodeinfo: state => state.instance.nodeinfo,
+      nodeinfo: state => state.instance.nodeinfo
     }),
     anonymousCanListen () {
       return _.get(this.nodeinfo, 'metadata.library.anonymousCanListen', false)
@@ -82,7 +139,7 @@ export default {
         // include hostname/protocol too so that the iframe link is absolute
         base = `${window.location.protocol}//${window.location.host}${base}`
       }
-      let instanceUrl = this.$store.state.instance.instanceUrl
+      const instanceUrl = this.$store.state.instance.instanceUrl
       let b = ''
       if (!window.location.href.startsWith(instanceUrl)) {
         // the frontend is running on a separate domain, so we need to provide
@@ -98,15 +155,15 @@ export default {
       return '100%'
     },
     embedCode () {
-      let src = this.iframeSrc.replace(/&/g, '&amp;')
+      const src = this.iframeSrc.replace(/&/g, '&amp;')
       return `<iframe width="${this.frameWidth}" height="${this.height}" scrolling="no" frameborder="no" src="${src}"></iframe>`
     }
   },
   methods: {
     copy () {
       this.$refs.textarea.select()
-      document.execCommand("Copy")
-      let self = this
+      document.execCommand('Copy')
+      const self = this
       self.copied = true
       this.timeout = setTimeout(() => {
         self.copied = false
diff --git a/front/src/components/audio/LibraryFollowButton.vue b/front/src/components/audio/LibraryFollowButton.vue
index 8aa0a8fe3..8190cca6e 100644
--- a/front/src/components/audio/LibraryFollowButton.vue
+++ b/front/src/components/audio/LibraryFollowButton.vue
@@ -1,16 +1,34 @@
- <template>
-  <button @click.stop="toggle" :class="['ui', 'pink', {'inverted': isApproved || isPending}, {'favorited': isApproved}, 'icon', 'labeled', 'button']">
-    <i class="heart icon"></i>
-    <translate v-if="isApproved" translate-context="Content/Library/Card.Button.Label/Verb">Unfollow</translate>
-    <translate v-else-if="isPending" translate-context="Content/Library/Card.Button.Label/Verb">Cancel follow request</translate>
-    <translate v-else translate-context="Content/Library/Card.Button.Label/Verb">Follow</translate>
+<template>
+  <button
+    :class="['ui', 'pink', {'inverted': isApproved || isPending}, {'favorited': isApproved}, 'icon', 'labeled', 'button']"
+    @click.stop="toggle"
+  >
+    <i class="heart icon" />
+    <translate
+      v-if="isApproved"
+      translate-context="Content/Library/Card.Button.Label/Verb"
+    >
+      Unfollow
+    </translate>
+    <translate
+      v-else-if="isPending"
+      translate-context="Content/Library/Card.Button.Label/Verb"
+    >
+      Cancel follow request
+    </translate>
+    <translate
+      v-else
+      translate-context="Content/Library/Card.Button.Label/Verb"
+    >
+      Follow
+    </translate>
   </button>
 </template>
 
 <script>
 export default {
   props: {
-    library: {type: Object},
+    library: { type: Object, required: true }
   },
   computed: {
     isPending () {
@@ -34,6 +52,5 @@ export default {
     }
   }
 
-
 }
 </script>
diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue
index dd0e65132..aba5a346d 100644
--- a/front/src/components/audio/PlayButton.vue
+++ b/front/src/components/audio/PlayButton.vue
@@ -1,52 +1,121 @@
 <template>
-  <span :title="title" :class="['ui', {'tiny': discrete}, {'icon': !discrete}, {'buttons': !dropdownOnly && !iconOnly}, 'play-button component-play-button']">
+  <span
+    :title="title"
+    :class="['ui', {'tiny': discrete}, {'icon': !discrete}, {'buttons': !dropdownOnly && !iconOnly}, 'play-button component-play-button']"
+  >
     <button
       v-if="!dropdownOnly"
-      @click.stop.prevent="replacePlay"
       :disabled="!playable"
       :aria-label="labels.replacePlay"
-      :class="buttonClasses.concat(['ui', {loading: isLoading}, {'mini': discrete}, {disabled: !playable}])">
-      <i v-if="playing" class="pause icon"></i>
-      <i v-else :class="[playIconClass, 'icon']"></i>
+      :class="buttonClasses.concat(['ui', {loading: isLoading}, {'mini': discrete}, {disabled: !playable}])"
+      @click.stop.prevent="replacePlay"
+    >
+      <i
+        v-if="playing"
+        class="pause icon"
+      />
+      <i
+        v-else
+        :class="[playIconClass, 'icon']"
+      />
       <template v-if="!discrete && !iconOnly">&nbsp;<slot><translate translate-context="*/Queue/Button.Label/Short, Verb">Play</translate></slot></template>
     </button>
     <button
       v-if="!discrete && !iconOnly"
+      :class="['ui', {disabled: !playable && !filterableArtist}, 'floating', 'dropdown', {'icon': !dropdownOnly}, {'button': !dropdownOnly}]"
       @click.stop.prevent="clicked = true"
-      :class="['ui', {disabled: !playable && !filterableArtist}, 'floating', 'dropdown', {'icon': !dropdownOnly}, {'button': !dropdownOnly}]">
-      <i :class="dropdownIconClasses.concat(['icon'])" :title="title" ></i>
-      <div class="menu" v-if="clicked">
-        <button class="item basic" ref="add" data-ref="add" :disabled="!playable" @click.stop.prevent="add" :title="labels.addToQueue">
-          <i class="plus icon"></i><translate translate-context="*/Queue/Dropdown/Button/Label/Short">Add to queue</translate>
+    >
+      <i
+        :class="dropdownIconClasses.concat(['icon'])"
+        :title="title"
+      />
+      <div
+        v-if="clicked"
+        class="menu"
+      >
+        <button
+          ref="add"
+          class="item basic"
+          data-ref="add"
+          :disabled="!playable"
+          :title="labels.addToQueue"
+          @click.stop.prevent="add"
+        >
+          <i class="plus icon" /><translate translate-context="*/Queue/Dropdown/Button/Label/Short">Add to queue</translate>
         </button>
-        <button class="item basic" ref="addNext" data-ref="addNext" :disabled="!playable" @click.stop.prevent="addNext()" :title="labels.playNext">
-          <i class="step forward icon"></i>{{ labels.playNext }}
+        <button
+          ref="addNext"
+          class="item basic"
+          data-ref="addNext"
+          :disabled="!playable"
+          :title="labels.playNext"
+          @click.stop.prevent="addNext()"
+        >
+          <i class="step forward icon" />{{ labels.playNext }}
         </button>
-        <button class="item basic" ref="playNow" data-ref="playNow" :disabled="!playable" @click.stop.prevent="addNext(true)" :title="labels.playNow">
-          <i class="play icon"></i>{{ labels.playNow }}
+        <button
+          ref="playNow"
+          class="item basic"
+          data-ref="playNow"
+          :disabled="!playable"
+          :title="labels.playNow"
+          @click.stop.prevent="addNext(true)"
+        >
+          <i class="play icon" />{{ labels.playNow }}
         </button>
-        <button v-if="track" class="item basic" :disabled="!playable" @click.stop.prevent="$store.dispatch('radios/start', {type: 'similar', objectId: track.id})" :title="labels.startRadio">
-          <i class="feed icon"></i><translate translate-context="*/Queue/Button.Label/Short, Verb">Play radio</translate>
+        <button
+          v-if="track"
+          class="item basic"
+          :disabled="!playable"
+          :title="labels.startRadio"
+          @click.stop.prevent="$store.dispatch('radios/start', {type: 'similar', objectId: track.id})"
+        >
+          <i class="feed icon" /><translate translate-context="*/Queue/Button.Label/Short, Verb">Play radio</translate>
         </button>
-        <button v-if="track" class="item basic" :disabled="!playable" @click.stop="$store.commit('playlists/chooseTrack', track)">
-          <i class="list icon"></i>
+        <button
+          v-if="track"
+          class="item basic"
+          :disabled="!playable"
+          @click.stop="$store.commit('playlists/chooseTrack', track)"
+        >
+          <i class="list icon" />
           <translate translate-context="Sidebar/Player/Icon.Tooltip/Verb">Add to playlist…</translate>
         </button>
-        <button v-if="track" class="item basic" @click.stop.prevent="$router.push(`/library/tracks/${track.id}/`)">
-          <i class="info icon"></i>
-          <translate v-if="track.artist.content_category === 'podcast'" translate-context="*/Queue/Dropdown/Button/Label/Short">Episode details</translate>
-          <translate v-else translate-context="*/Queue/Dropdown/Button/Label/Short">Track details</translate>
+        <button
+          v-if="track"
+          class="item basic"
+          @click.stop.prevent="$router.push(`/library/tracks/${track.id}/`)"
+        >
+          <i class="info icon" />
+          <translate
+            v-if="track.artist.content_category === 'podcast'"
+            translate-context="*/Queue/Dropdown/Button/Label/Short"
+          >Episode details</translate>
+          <translate
+            v-else
+            translate-context="*/Queue/Dropdown/Button/Label/Short"
+          >Track details</translate>
         </button>
-        <div class="divider"></div>
-        <button v-if="filterableArtist" ref="filterArtist" data-ref="filterArtist" class="item basic" :disabled="!filterableArtist" @click.stop.prevent="filterArtist" :title="labels.hideArtist">
-          <i class="eye slash outline icon"></i><translate translate-context="*/Queue/Dropdown/Button/Label/Short">Hide content from this artist</translate>
+        <div class="divider" />
+        <button
+          v-if="filterableArtist"
+          ref="filterArtist"
+          data-ref="filterArtist"
+          class="item basic"
+          :disabled="!filterableArtist"
+          :title="labels.hideArtist"
+          @click.stop.prevent="filterArtist"
+        >
+          <i class="eye slash outline icon" /><translate translate-context="*/Queue/Dropdown/Button/Label/Short">Hide content from this artist</translate>
         </button>
         <button
           v-for="obj in getReportableObjs({track, album, artist, playlist, account, channel})"
           :key="obj.target.type + obj.target.id"
+          :ref="`report${obj.target.type}${obj.target.id}`"
           class="item basic"
-          :ref="`report${obj.target.type}${obj.target.id}`" :data-ref="`report${obj.target.type}${obj.target.id}`"
-          @click.stop.prevent="$store.dispatch('moderation/report', obj.target)">
+          :data-ref="`report${obj.target.type}${obj.target.id}`"
+          @click.stop.prevent="$store.dispatch('moderation/report', obj.target)"
+        >
           <i class="share icon" /> {{ obj.label }}
         </button>
       </div>
@@ -55,7 +124,6 @@
 </template>
 
 <script>
-import axios from 'axios'
 import jQuery from 'jquery'
 
 import ReportMixin from '@/components/mixins/Report'
@@ -65,23 +133,23 @@ export default {
   mixins: [ReportMixin, PlayOptionsMixin],
   props: {
     // we can either have a single or multiple tracks to play when clicked
-    tracks: {type: Array, required: false},
-    track: {type: Object, required: false},
-    account: {type: Object, required: false},
-    dropdownIconClasses: {type: Array, required: false, default: () => { return ['dropdown'] }},
-    playIconClass: {type: String, required: false, default: 'play icon'},
-    buttonClasses: {type: Array, required: false, default: () => { return ['button'] }},
-    playlist: {type: Object, required: false},
-    discrete: {type: Boolean, default: false},
-    dropdownOnly: {type: Boolean, default: false},
-    iconOnly: {type: Boolean, default: false},
-    artist: {type: Object, required: false},
-    album: {type: Object, required: false},
-    library: {type: Object, required: false},
-    channel: {type: Object, required: false},
-    isPlayable: {type: Boolean, required: false, default: null},
-    playing: {type: Boolean, required: false, default: false},
-    paused: {type: Boolean, required: false, default: false}
+    tracks: { type: Array, required: false, default: () => { return [] } },
+    track: { type: Object, required: false, default: () => { return {} } },
+    account: { type: Object, required: false, default: () => { return {} } },
+    dropdownIconClasses: { type: Array, required: false, default: () => { return ['dropdown'] } },
+    playIconClass: { type: String, required: false, default: 'play icon' },
+    buttonClasses: { type: Array, required: false, default: () => { return ['button'] } },
+    playlist: { type: Object, required: false, default: () => { return {} } },
+    discrete: { type: Boolean, default: false },
+    dropdownOnly: { type: Boolean, default: false },
+    iconOnly: { type: Boolean, default: false },
+    artist: { type: Object, required: false, default: () => { return {} } },
+    album: { type: Object, required: false, default: () => { return {} } },
+    library: { type: Object, required: false, default: () => { return {} } },
+    channel: { type: Object, required: false, default: () => { return {} } },
+    isPlayable: { type: Boolean, required: false, default: null },
+    playing: { type: Boolean, required: false, default: false },
+    paused: { type: Boolean, required: false, default: false }
   },
   data () {
     return {
@@ -111,7 +179,7 @@ export default {
         startRadio: this.$pgettext('*/Queue/Dropdown/Button/Title', 'Play similar songs'),
         report: this.$pgettext('*/Moderation/*/Button/Label,Verb', 'Report…'),
         addToPlaylist: this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Add to playlist…'),
-        replacePlay,
+        replacePlay
       }
     },
     title () {
@@ -122,43 +190,42 @@ export default {
           return this.$pgettext('*/Queue/Button/Title', 'This track is not available in any library you have access to')
         }
       }
-    },
+      return null
+    }
   },
   watch: {
     clicked () {
-      let self = this
+      const self = this
       this.$nextTick(() => {
         jQuery(this.$el).find('.ui.dropdown').dropdown({
           selectOnKeydown: false,
           action: function (text, value, $el) {
             // used to ensure focusing the dropdown and clicking via keyboard
             // works as expected
-            let button = self.$refs[$el.data('ref')]
+            const button = self.$refs[$el.data('ref')]
             if (Array.isArray(button)) {
               button[0].click()
             } else {
               button.click()
             }
             jQuery(self.$el).find('.ui.dropdown').dropdown('hide')
-          },
+          }
         })
         jQuery(this.$el).find('.ui.dropdown').dropdown('show', function () {
           // little magic to ensure the menu is always visible in the viewport
           // By default, try to diplay it on the right if there is enough room
-          let menu = jQuery(self.$el).find('.ui.dropdown').find(".menu")
-          let viewportOffset = menu.get(0).getBoundingClientRect();
-          let left = viewportOffset.left;
-          let viewportWidth = document.documentElement.clientWidth
-          let rightOverflow = viewportOffset.right - viewportWidth
-          let leftOverflow = -viewportOffset.left
+          const menu = jQuery(self.$el).find('.ui.dropdown').find('.menu')
+          const viewportOffset = menu.get(0).getBoundingClientRect()
+          const viewportWidth = document.documentElement.clientWidth
+          const rightOverflow = viewportOffset.right - viewportWidth
+          const leftOverflow = -viewportOffset.left
           let offset = 0
           if (rightOverflow > 0) {
             offset = -rightOverflow - 5
-            menu.css({cssText: `left: ${offset}px !important;`});
-          }
-          else if (leftOverflow > 0) {
-            offset = leftOverflow  + 5
-            menu.css({cssText: `right: -${offset}px !important;`});
+            menu.css({ cssText: `left: ${offset}px !important;` })
+          } else if (leftOverflow > 0) {
+            offset = leftOverflow + 5
+            menu.css({ cssText: `right: -${offset}px !important;` })
           }
         })
       })
diff --git a/front/src/components/audio/Player.vue b/front/src/components/audio/Player.vue
index fce05c8d7..d4a9d658f 100644
--- a/front/src/components/audio/Player.vue
+++ b/front/src/components/audio/Player.vue
@@ -1,64 +1,144 @@
 <template>
-  <section role="complementary" v-if="currentTrack" class="player-wrapper ui bottom-player component-player" aria-labelledby="player-label">
-    <h1 id="player-label" class="visually-hidden">
-      <translate translate-context="*/*/*">Audio player and controls</translate>
+  <section
+    v-if="currentTrack"
+    role="complementary"
+    class="player-wrapper ui bottom-player component-player"
+    aria-labelledby="player-label"
+  >
+    <h1
+      id="player-label"
+      class="visually-hidden"
+    >
+      <translate translate-context="*/*/*">
+        Audio player and controls
+      </translate>
     </h1>
-    <div class="ui inverted segment fixed-controls" @click.prevent.stop="toggleMobilePlayer">
+    <div
+      class="ui inverted segment fixed-controls"
+      @click.prevent.stop="toggleMobilePlayer"
+    >
       <div
-        :class="['ui', 'top attached', 'small', 'inverted', {'indicating': isLoadingAudio}, 'progress']">
-        <div class="buffer bar" :data-percent="bufferProgress" :style="{ 'width': bufferProgress + '%' }"></div>
-        <div class="position bar" :data-percent="progress" :style="{ 'width': progress + '%' }"></div>
+        :class="['ui', 'top attached', 'small', 'inverted', {'indicating': isLoadingAudio}, 'progress']"
+      >
+        <div
+          class="buffer bar"
+          :data-percent="bufferProgress"
+          :style="{ 'width': bufferProgress + '%' }"
+        />
+        <div
+          class="position bar"
+          :data-percent="progress"
+          :style="{ 'width': progress + '%' }"
+        />
       </div>
       <div class="controls-row">
-
         <div class="controls track-controls queue-not-focused desktop-and-up">
-          <div class="ui tiny image" @click.stop.prevent="$router.push({name: 'library.tracks.detail', params: {id: currentTrack.id }})">
-            <img alt="" ref="cover" v-if="currentTrack.cover && currentTrack.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.urls.medium_square_crop)">
-            <img alt="" ref="cover" v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.urls && currentTrack.album.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.urls.medium_square_crop)">
-            <img alt="" v-else src="../../assets/audio/default-cover.png">
+          <div
+            class="ui tiny image"
+            @click.stop.prevent="$router.push({name: 'library.tracks.detail', params: {id: currentTrack.id }})"
+          >
+            <img
+              v-if="currentTrack.cover && currentTrack.cover.urls.original"
+              ref="cover"
+              alt=""
+              :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.urls.medium_square_crop)"
+            >
+            <img
+              v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.urls && currentTrack.album.cover.urls.original"
+              ref="cover"
+              alt=""
+              :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.urls.medium_square_crop)"
+            >
+            <img
+              v-else
+              alt=""
+              src="../../assets/audio/default-cover.png"
+            >
           </div>
-          <div @click.stop.prevent="" class="middle aligned content ellipsis">
+          <div
+            class="middle aligned content ellipsis"
+            @click.stop.prevent=""
+          >
             <strong>
-              <router-link @click.stop.prevent="" class="small header discrete link track" :to="{name: 'library.tracks.detail', params: {id: currentTrack.id }}">
+              <router-link
+                class="small header discrete link track"
+                :to="{name: 'library.tracks.detail', params: {id: currentTrack.id }}"
+                @click.stop.prevent=""
+              >
                 {{ currentTrack.title }}
               </router-link>
             </strong>
             <div class="meta">
-              <router-link @click.stop.prevent="" class="discrete link" :to="{name: 'library.artists.detail', params: {id: currentTrack.artist.id }}">{{ currentTrack.artist.name }}</router-link>
-              <template v-if="currentTrack.album"> /
-                <router-link @click.stop.prevent="" class="discrete link" :to="{name: 'library.albums.detail', params: {id: currentTrack.album.id }}">{{ currentTrack.album.title }}</router-link>
+              <router-link
+                class="discrete link"
+                :to="{name: 'library.artists.detail', params: {id: currentTrack.artist.id }}"
+                @click.stop.prevent=""
+              >
+                {{ currentTrack.artist.name }}
+              </router-link>
+              <template v-if="currentTrack.album">
+                /
+                <router-link
+                  class="discrete link"
+                  :to="{name: 'library.albums.detail', params: {id: currentTrack.album.id }}"
+                  @click.stop.prevent=""
+                >
+                  {{ currentTrack.album.title }}
+                </router-link>
               </template>
             </div>
           </div>
         </div>
         <div class="controls track-controls queue-not-focused tablet-and-below">
           <div class="ui tiny image">
-            <img alt="" ref="cover" v-if="currentTrack.cover && currentTrack.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.urls.medium_square_crop)">
-            <img alt="" ref="cover" v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.urls.original" :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.urls.medium_square_crop)">
-            <img alt="" v-else src="../../assets/audio/default-cover.png">
+            <img
+              v-if="currentTrack.cover && currentTrack.cover.urls.original"
+              ref="cover"
+              alt=""
+              :src="$store.getters['instance/absoluteUrl'](currentTrack.cover.urls.medium_square_crop)"
+            >
+            <img
+              v-else-if="currentTrack.album && currentTrack.album.cover && currentTrack.album.cover.urls.original"
+              ref="cover"
+              alt=""
+              :src="$store.getters['instance/absoluteUrl'](currentTrack.album.cover.urls.medium_square_crop)"
+            >
+            <img
+              v-else
+              alt=""
+              src="../../assets/audio/default-cover.png"
+            >
           </div>
           <div class="middle aligned content ellipsis">
             <strong>
               {{ currentTrack.title }}
             </strong>
             <div class="meta">
-              {{ currentTrack.artist.name }}<template v-if="currentTrack.album"> / {{ currentTrack.album.title }}</template>
+              {{ currentTrack.artist.name }}<template v-if="currentTrack.album">
+                / {{ currentTrack.album.title }}
+              </template>
             </div>
           </div>
         </div>
-        <div class="controls desktop-and-up fluid align-right" v-if="$store.state.auth.authenticated">
+        <div
+          v-if="$store.state.auth.authenticated"
+          class="controls desktop-and-up fluid align-right"
+        >
           <track-favorite-icon
             class="control white"
-            :track="currentTrack"></track-favorite-icon>
+            :track="currentTrack"
+          />
           <track-playlist-icon
             class="control white"
-            :track="currentTrack"></track-playlist-icon>
+            :track="currentTrack"
+          />
           <button
-            @click="$store.dispatch('moderation/hide', {type: 'artist', target: currentTrack.artist})"
             :class="['ui', 'really', 'basic', 'circular', 'icon', 'button', 'control']"
             :aria-label="labels.addArtistContentFilter"
-            :title="labels.addArtistContentFilter">
-            <i :class="['eye slash outline', 'basic', 'icon']"></i>
+            :title="labels.addArtistContentFilter"
+            @click="$store.dispatch('moderation/hide', {type: 'artist', target: currentTrack.artist})"
+          >
+            <i :class="['eye slash outline', 'basic', 'icon']" />
           </button>
         </div>
         <div class="player-controls controls queue-not-focused">
@@ -66,41 +146,48 @@
             :title="labels.previous"
             :aria-label="labels.previous"
             class="circular button control tablet-and-up"
+            :disabled="!hasPrevious"
             @click.prevent.stop="$store.dispatch('queue/previous')"
-            :disabled="!hasPrevious">
-              <i :class="['ui', 'large', {'disabled': !hasPrevious}, 'backward step', 'icon']" ></i>
+          >
+            <i :class="['ui', 'large', {'disabled': !hasPrevious}, 'backward step', 'icon']" />
           </button>
           <button
             v-if="!playing"
             :title="labels.play"
             :aria-label="labels.play"
+            class="circular button control"
             @click.prevent.stop="resumePlayback"
-            class="circular button control">
-              <i :class="['ui', 'big', 'play', {'disabled': !currentTrack}, 'icon']"></i>
+          >
+            <i :class="['ui', 'big', 'play', {'disabled': !currentTrack}, 'icon']" />
           </button>
           <button
             v-else
             :title="labels.pause"
             :aria-label="labels.pause"
+            class="circular button control"
             @click.prevent.stop="pausePlayback"
-            class="circular button control">
-              <i :class="['ui', 'big', 'pause', {'disabled': !currentTrack}, 'icon']"></i>
+          >
+            <i :class="['ui', 'big', 'pause', {'disabled': !currentTrack}, 'icon']" />
           </button>
           <button
             :title="labels.next"
             :aria-label="labels.next"
             class="circular button control"
+            :disabled="!hasNext"
             @click.prevent.stop="$store.dispatch('queue/next')"
-            :disabled="!hasNext">
-              <i :class="['ui', 'large', {'disabled': !hasNext}, 'forward step', 'icon']" ></i>
+          >
+            <i :class="['ui', 'large', {'disabled': !hasNext}, 'forward step', 'icon']" />
           </button>
         </div>
 
         <div class="controls progress-controls queue-not-focused tablet-and-up small align-left">
           <div class="timer">
             <template v-if="!isLoadingAudio">
-              <span class="start" @click.stop.prevent="setCurrentTime(0)">{{currentTimeFormatted}}</span>
-              | <span class="total">{{durationFormatted}}</span>
+              <span
+                class="start"
+                @click.stop.prevent="setCurrentTime(0)"
+              >{{ currentTimeFormatted }}</span>
+              | <span class="total">{{ durationFormatted }}</span>
             </template>
             <template v-else>
               00:00 | 00:00
@@ -111,35 +198,40 @@
           <div class="group">
             <volume-control class="expandable" />
             <button
-              class="circular control button"
               v-if="looping === 0"
+              class="circular control button"
               :title="labels.loopingDisabled"
               :aria-label="labels.loopingDisabled"
+              :disabled="!currentTrack"
               @click.prevent.stop="$store.commit('player/looping', 1)"
-              :disabled="!currentTrack">
-              <i :class="['ui', {'disabled': !currentTrack}, 'step', 'repeat', 'icon']"></i>
+            >
+              <i :class="['ui', {'disabled': !currentTrack}, 'step', 'repeat', 'icon']" />
             </button>
             <button
+              v-if="looping === 1"
               class="looping circular control button"
-              @click.prevent.stop="$store.commit('player/looping', 2)"
               :title="labels.loopingSingle"
               :aria-label="labels.loopingSingle"
-              v-if="looping === 1"
-              :disabled="!currentTrack">
+              :disabled="!currentTrack"
+              @click.prevent.stop="$store.commit('player/looping', 2)"
+            >
               <i
-                class="repeat icon">
+                class="repeat icon"
+              >
                 <span class="ui circular tiny vibrant label">1</span>
               </i>
             </button>
             <button
+              v-if="looping === 2"
               class="looping circular control button"
               :title="labels.loopingWhole"
               :aria-label="labels.loopingWhole"
-              v-if="looping === 2"
               :disabled="!currentTrack"
-              @click.prevent.stop="$store.commit('player/looping', 0)">
+              @click.prevent.stop="$store.commit('player/looping', 0)"
+            >
               <i
-                class="repeat icon">
+                class="repeat icon"
+              >
                 <span class="ui circular tiny vibrant label">&infin;</span>
               </i>
             </button>
@@ -148,55 +240,80 @@
               :disabled="queue.tracks.length === 0"
               :title="labels.shuffle"
               :aria-label="labels.shuffle"
-              @click.prevent.stop="shuffle()">
-              <div v-if="isShuffling" class="ui inline shuffling inverted tiny active loader"></div>
-              <i v-else :class="['ui', 'random', {'disabled': queue.tracks.length === 0}, 'icon']" ></i>
+              @click.prevent.stop="shuffle()"
+            >
+              <div
+                v-if="isShuffling"
+                class="ui inline shuffling inverted tiny active loader"
+              />
+              <i
+                v-else
+                :class="['ui', 'random', {'disabled': queue.tracks.length === 0}, 'icon']"
+              />
             </button>
           </div>
           <div class="group">
             <div class="fake-dropdown">
-              <button class="position circular control button desktop-and-up" @click.stop="toggleMobilePlayer" aria-expanded="true">
-                <i class="stream icon"></i>
-                <translate translate-context="Sidebar/Queue/Text" :translate-params="{index: queue.currentIndex + 1, length: queue.tracks.length}">
+              <button
+                class="position circular control button desktop-and-up"
+                aria-expanded="true"
+                @click.stop="toggleMobilePlayer"
+              >
+                <i class="stream icon" />
+                <translate
+                  translate-context="Sidebar/Queue/Text"
+                  :translate-params="{index: queue.currentIndex + 1, length: queue.tracks.length}"
+                >
                   %{ index } of %{ length }
                 </translate>
               </button>
-              <button class="position circular control button tablet-and-below" @click.stop="switchTab">
-                <i class="stream icon"></i>
-                <translate translate-context="Sidebar/Queue/Text" :translate-params="{index: queue.currentIndex + 1, length: queue.tracks.length}">
+              <button
+                class="position circular control button tablet-and-below"
+                @click.stop="switchTab"
+              >
+                <i class="stream icon" />
+                <translate
+                  translate-context="Sidebar/Queue/Text"
+                  :translate-params="{index: queue.currentIndex + 1, length: queue.tracks.length}"
+                >
                   %{ index } of %{ length }
                 </translate>
               </button>
 
               <button
-                class="circular control button close-control desktop-and-up"
                 v-if="$store.state.ui.queueFocused"
-                @click.stop="toggleMobilePlayer">
-                <i class="large down angle icon"></i>
+                class="circular control button close-control desktop-and-up"
+                @click.stop="toggleMobilePlayer"
+              >
+                <i class="large down angle icon" />
               </button>
               <button
-                class="circular control button desktop-and-up"
                 v-else
-                @click.stop="toggleMobilePlayer">
-                <i class="large up angle icon"></i>
+                class="circular control button desktop-and-up"
+                @click.stop="toggleMobilePlayer"
+              >
+                <i class="large up angle icon" />
               </button>
               <button
-                class="circular control button close-control tablet-and-below"
                 v-if="$store.state.ui.queueFocused === 'player'"
-                @click.stop="switchTab">
-                <i class="large up angle icon"></i>
+                class="circular control button close-control tablet-and-below"
+                @click.stop="switchTab"
+              >
+                <i class="large up angle icon" />
               </button>
               <button
-                class="circular control button tablet-and-below"
                 v-if="$store.state.ui.queueFocused === 'queue'"
-                @click.stop="switchTab">
-                <i class="large down angle icon"></i>
+                class="circular control button tablet-and-below"
+                @click.stop="switchTab"
+              >
+                <i class="large down angle icon" />
               </button>
             </div>
             <button
               class="circular control button close-control tablet-and-below"
-              @click.stop="$store.commit('ui/queueFocused', null)">
-              <i class="x icon"></i>
+              @click.stop="$store.commit('ui/queueFocused', null)"
+            >
+              <i class="x icon" />
             </button>
           </div>
         </div>
@@ -219,7 +336,7 @@
       @keydown.f.exact="$store.dispatch('favorites/toggle', currentTrack.id)"
       @keydown.q.exact="clean"
       @keydown.e.exact="toggleMobilePlayer"
-      />
+    />
   </section>
 </template>
 
@@ -259,6 +376,124 @@ export default {
       nextTrackPreloaded: false
     }
   },
+  computed: {
+    ...mapState({
+      currentIndex: state => state.queue.currentIndex,
+      playing: state => state.player.playing,
+      isLoadingAudio: state => state.player.isLoadingAudio,
+      volume: state => state.player.volume,
+      looping: state => state.player.looping,
+      duration: state => state.player.duration,
+      bufferProgress: state => state.player.bufferProgress,
+      errored: state => state.player.errored,
+      currentTime: state => state.player.currentTime,
+      queue: state => state.queue
+    }),
+    ...mapGetters({
+      currentTrack: 'queue/currentTrack',
+      hasNext: 'queue/hasNext',
+      hasPrevious: 'queue/hasPrevious',
+      emptyQueue: 'queue/isEmpty',
+      durationFormatted: 'player/durationFormatted',
+      currentTimeFormatted: 'player/currentTimeFormatted',
+      progress: 'player/progress'
+    }),
+    updateProgressThrottled () {
+      return _.throttle(this.updateProgress, 50)
+    },
+    labels () {
+      const audioPlayer = this.$pgettext('Sidebar/Player/Hidden text', 'Media player')
+      const previous = this.$pgettext('Sidebar/Player/Icon.Tooltip', 'Previous track')
+      const play = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Play')
+      const pause = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Pause')
+      const next = this.$pgettext('Sidebar/Player/Icon.Tooltip', 'Next track')
+      const unmute = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Unmute')
+      const mute = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Mute')
+      const expandQueue = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Expand queue')
+      const loopingDisabled = this.$pgettext('Sidebar/Player/Icon.Tooltip',
+        'Looping disabled. Click to switch to single-track looping.'
+      )
+      const loopingSingle = this.$pgettext('Sidebar/Player/Icon.Tooltip',
+        'Looping on a single track. Click to switch to whole queue looping.'
+      )
+      const loopingWhole = this.$pgettext('Sidebar/Player/Icon.Tooltip',
+        'Looping on whole queue. Click to disable looping.'
+      )
+      const shuffle = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Shuffle your queue')
+      const clear = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Clear your queue')
+      const addArtistContentFilter = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Hide content from this artist…')
+      return {
+        audioPlayer,
+        previous,
+        play,
+        pause,
+        next,
+        unmute,
+        mute,
+        loopingDisabled,
+        loopingSingle,
+        loopingWhole,
+        shuffle,
+        clear,
+        expandQueue,
+        addArtistContentFilter
+      }
+    }
+  },
+  watch: {
+    currentTrack: {
+      async handler (newValue, oldValue) {
+        if (newValue === oldValue) {
+          return
+        }
+        this.nextTrackPreloaded = false
+        clearTimeout(this.playTimeout)
+        if (this.currentSound) {
+          this.currentSound.pause()
+        }
+        this.$store.commit('player/isLoadingAudio', true)
+        this.playTimeout = setTimeout(async () => {
+          await this.loadSound(newValue, oldValue)
+        }, 100)
+        this.updateMetadata()
+      },
+      immediate: false
+    },
+    volume: {
+      immediate: true,
+      handler (newValue) {
+        this.sliderVolume = newValue
+        Howler.volume(toLinearVolumeScale(newValue))
+      }
+    },
+    sliderVolume (newValue) {
+      this.$store.commit('player/volume', newValue)
+    },
+    playing: async function (newValue) {
+      if (this.currentSound) {
+        if (newValue === true) {
+          this.soundId = this.currentSound.play(this.soundId)
+        } else {
+          this.currentSound.pause(this.soundId)
+        }
+      } else {
+        await this.loadSound(this.currentTrack, null)
+      }
+
+      this.observeProgress(newValue)
+    },
+    currentTime (newValue) {
+      if (!this.isUpdatingTime) {
+        this.setCurrentTime(newValue)
+      }
+      this.isUpdatingTime = false
+    },
+    emptyQueue (newValue) {
+      if (newValue) {
+        Howler.unload()
+      }
+    }
+  },
   mounted () {
     this.$store.dispatch('player/updateProgress', 0)
     this.$store.commit('player/playing', false)
@@ -661,124 +896,6 @@ export default {
         navigator.mediaSession.metadata = new window.MediaMetadata(metadata)
       }
     }
-  },
-  computed: {
-    ...mapState({
-      currentIndex: state => state.queue.currentIndex,
-      playing: state => state.player.playing,
-      isLoadingAudio: state => state.player.isLoadingAudio,
-      volume: state => state.player.volume,
-      looping: state => state.player.looping,
-      duration: state => state.player.duration,
-      bufferProgress: state => state.player.bufferProgress,
-      errored: state => state.player.errored,
-      currentTime: state => state.player.currentTime,
-      queue: state => state.queue
-    }),
-    ...mapGetters({
-      currentTrack: 'queue/currentTrack',
-      hasNext: 'queue/hasNext',
-      hasPrevious: 'queue/hasPrevious',
-      emptyQueue: 'queue/isEmpty',
-      durationFormatted: 'player/durationFormatted',
-      currentTimeFormatted: 'player/currentTimeFormatted',
-      progress: 'player/progress'
-    }),
-    updateProgressThrottled () {
-      return _.throttle(this.updateProgress, 50)
-    },
-    labels () {
-      const audioPlayer = this.$pgettext('Sidebar/Player/Hidden text', 'Media player')
-      const previous = this.$pgettext('Sidebar/Player/Icon.Tooltip', 'Previous track')
-      const play = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Play')
-      const pause = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Pause')
-      const next = this.$pgettext('Sidebar/Player/Icon.Tooltip', 'Next track')
-      const unmute = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Unmute')
-      const mute = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Mute')
-      const expandQueue = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Expand queue')
-      const loopingDisabled = this.$pgettext('Sidebar/Player/Icon.Tooltip',
-        'Looping disabled. Click to switch to single-track looping.'
-      )
-      const loopingSingle = this.$pgettext('Sidebar/Player/Icon.Tooltip',
-        'Looping on a single track. Click to switch to whole queue looping.'
-      )
-      const loopingWhole = this.$pgettext('Sidebar/Player/Icon.Tooltip',
-        'Looping on whole queue. Click to disable looping.'
-      )
-      const shuffle = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Shuffle your queue')
-      const clear = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Clear your queue')
-      const addArtistContentFilter = this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Hide content from this artist…')
-      return {
-        audioPlayer,
-        previous,
-        play,
-        pause,
-        next,
-        unmute,
-        mute,
-        loopingDisabled,
-        loopingSingle,
-        loopingWhole,
-        shuffle,
-        clear,
-        expandQueue,
-        addArtistContentFilter
-      }
-    }
-  },
-  watch: {
-    currentTrack: {
-      async handler (newValue, oldValue) {
-        if (newValue === oldValue) {
-          return
-        }
-        this.nextTrackPreloaded = false
-        clearTimeout(this.playTimeout)
-        if (this.currentSound) {
-          this.currentSound.pause()
-        }
-        this.$store.commit('player/isLoadingAudio', true)
-        this.playTimeout = setTimeout(async () => {
-          await this.loadSound(newValue, oldValue)
-        }, 100)
-        this.updateMetadata()
-      },
-      immediate: false
-    },
-    volume: {
-      immediate: true,
-      handler (newValue) {
-        this.sliderVolume = newValue
-        Howler.volume(toLinearVolumeScale(newValue))
-      }
-    },
-    sliderVolume (newValue) {
-      this.$store.commit('player/volume', newValue)
-    },
-    playing: async function (newValue) {
-      if (this.currentSound) {
-        if (newValue === true) {
-          this.soundId = this.currentSound.play(this.soundId)
-        } else {
-          this.currentSound.pause(this.soundId)
-        }
-      } else {
-        await this.loadSound(this.currentTrack, null)
-      }
-
-      this.observeProgress(newValue)
-    },
-    currentTime (newValue) {
-      if (!this.isUpdatingTime) {
-        this.setCurrentTime(newValue)
-      }
-      this.isUpdatingTime = false
-    },
-    emptyQueue (newValue) {
-      if (newValue) {
-        Howler.unload()
-      }
-    }
   }
 }
 </script>
diff --git a/front/src/components/audio/Search.vue b/front/src/components/audio/Search.vue
index 85960eb38..a27018da5 100644
--- a/front/src/components/audio/Search.vue
+++ b/front/src/components/audio/Search.vue
@@ -1,29 +1,69 @@
 <template>
   <div>
-    <h2><translate translate-context="Content/Search/Title">Search for some music</translate></h2>
+    <h2>
+      <translate translate-context="Content/Search/Title">
+        Search for some music
+      </translate>
+    </h2>
     <div :class="['ui', {'loading': isLoading }, 'search']">
       <div class="ui icon big input">
-        <i class="search icon"></i>
-        <input ref="search" class="prompt" :placeholder="labels.searchPlaceholder" v-model.trim="query" type="text" />
+        <i class="search icon" />
+        <input
+          ref="search"
+          v-model.trim="query"
+          class="prompt"
+          :placeholder="labels.searchPlaceholder"
+          type="text"
+        >
       </div>
     </div>
     <template v-if="query.length > 0">
-      <h3 class="ui title"><translate translate-context="*/*/*/Noun">Artists</translate></h3>
+      <h3 class="ui title">
+        <translate translate-context="*/*/*/Noun">
+          Artists
+        </translate>
+      </h3>
       <div v-if="results.artists.length > 0">
         <div class="ui cards">
-          <artist-card :key="artist.id" v-for="artist in results.artists" :artist="artist" ></artist-card>
+          <artist-card
+            v-for="artist in results.artists"
+            :key="artist.id"
+            :artist="artist"
+          />
         </div>
       </div>
-      <p v-else><translate translate-context="Content/Search/Paragraph">No artist matched your query</translate></p>
+      <p v-else>
+        <translate translate-context="Content/Search/Paragraph">
+          No artist matched your query
+        </translate>
+      </p>
     </template>
     <template v-if="query.length > 0">
-      <h3 class="ui title"><translate translate-context="*/*/*">Albums</translate></h3>
-      <div v-if="results.albums.length > 0" class="ui stackable three column grid">
-        <div class="column" :key="album.id" v-for="album in results.albums">
-          <album-card class="fluid" :album="album" ></album-card>
+      <h3 class="ui title">
+        <translate translate-context="*/*/*">
+          Albums
+        </translate>
+      </h3>
+      <div
+        v-if="results.albums.length > 0"
+        class="ui stackable three column grid"
+      >
+        <div
+          v-for="album in results.albums"
+          :key="album.id"
+          class="column"
+        >
+          <album-card
+            class="fluid"
+            :album="album"
+          />
         </div>
       </div>
-      <p v-else><translate translate-context="Content/Search/Paragraph">No album matched your query</translate></p>
+      <p v-else>
+        <translate translate-context="Content/Search/Paragraph">
+          No album matched your query
+        </translate>
+      </p>
     </template>
   </div>
 </template>
@@ -41,7 +81,7 @@ export default {
     ArtistCard
   },
   props: {
-    autofocus: {type: Boolean, default: false}
+    autofocus: { type: Boolean, default: false }
   },
   data () {
     return {
@@ -53,12 +93,6 @@ export default {
       isLoading: false
     }
   },
-  mounted () {
-    if (this.autofocus) {
-      this.$refs.search.focus()
-    }
-    this.search()
-  },
   computed: {
     labels () {
       return {
@@ -66,15 +100,26 @@ export default {
       }
     }
   },
+  watch: {
+    query () {
+      this.search()
+    }
+  },
+  mounted () {
+    if (this.autofocus) {
+      this.$refs.search.focus()
+    }
+    this.search()
+  },
   methods: {
     search: _.debounce(function () {
       if (this.query.length < 1) {
         return
       }
-      var self = this
+      const self = this
       self.isLoading = true
       logger.default.debug('Searching track matching "' + this.query + '"')
-      let params = {
+      const params = {
         query: this.query
       }
       axios.get('search', {
@@ -90,11 +135,6 @@ export default {
         artists: results.artists
       }
     }
-  },
-  watch: {
-    query () {
-      this.search()
-    }
   }
 }
 </script>
diff --git a/front/src/components/audio/SearchBar.vue b/front/src/components/audio/SearchBar.vue
index d254ac645..5690aa742 100644
--- a/front/src/components/audio/SearchBar.vue
+++ b/front/src/components/audio/SearchBar.vue
@@ -1,11 +1,19 @@
 <template>
   <div class="ui fluid category search">
-    <slot></slot><div class="ui icon input">
-      <input :aria-label="labels.searchContent" ref="search" type="search" class="prompt" name="search" :placeholder="labels.placeholder" @keydown.esc="$event.target.blur()">
-      <i class="search icon"></i>
+    <slot /><div class="ui icon input">
+      <input
+        ref="search"
+        :aria-label="labels.searchContent"
+        type="search"
+        class="prompt"
+        name="search"
+        :placeholder="labels.placeholder"
+        @keydown.esc="$event.target.blur()"
+      >
+      <i class="search icon" />
     </div>
-    <div class="results"></div>
-    <slot name="after"></slot>
+    <div class="results" />
+    <slot name="after" />
     <GlobalEvents
       @keydown.shift.f.prevent.exact="focusSearch"
     />
@@ -16,11 +24,11 @@
 import jQuery from 'jquery'
 import router from '@/router'
 import lodash from '@/lodash'
-import GlobalEvents from "@/components/utils/global-events"
+import GlobalEvents from '@/components/utils/global-events'
 
 export default {
   components: {
-  GlobalEvents,
+    GlobalEvents
   },
   computed: {
     labels () {
@@ -31,22 +39,21 @@ export default {
     }
   },
   mounted () {
-    let artistLabel = this.$pgettext('*/*/*/Noun', 'Artist')
-    let albumLabel = this.$pgettext('*/*/*', 'Album')
-    let trackLabel = this.$pgettext('*/*/*/Noun', 'Track')
-    let tagLabel = this.$pgettext('*/*/*/Noun', 'Tag')
-    let self = this
-    var searchQuery;
+    const artistLabel = this.$pgettext('*/*/*/Noun', 'Artist')
+    const albumLabel = this.$pgettext('*/*/*', 'Album')
+    const trackLabel = this.$pgettext('*/*/*/Noun', 'Track')
+    const tagLabel = this.$pgettext('*/*/*/Noun', 'Tag')
+    const self = this
+    let searchQuery
 
-    jQuery(this.$el).keypress(function(e) {
-      if(e.which == 13) {
+    jQuery(this.$el).keypress(function (e) {
+      if (e.which === 13) {
         // Cancel any API search request to backend…
-        jQuery(this.$el).search('cancel query');
+        jQuery(this.$el).search('cancel query')
         // Go direct to the artist page…
-        router.push(`/search?q=${searchQuery}&type=artists`);
-	}
-    });
-
+        router.push(`/search?q=${searchQuery}&type=artists`)
+      }
+    })
 
     jQuery(this.$el).search({
       type: 'category',
@@ -57,9 +64,9 @@ export default {
         noResults: this.$pgettext('Sidebar/Search/Error.Label', 'Sorry, there are no results for this search')
       },
       onSelect (result, response) {
-        jQuery(self.$el).search("set value", searchQuery)
+        jQuery(self.$el).search('set value', searchQuery)
         router.push(result.routerUrl)
-        jQuery(self.$el).search("hide results")
+        jQuery(self.$el).search('hide results')
         return false
       },
       onSearchQuery (query) {
@@ -78,17 +85,17 @@ export default {
           return xhrObject
         },
         onResponse: function (initialResponse) {
-          let objId = self.extractObjId(searchQuery)
-          let results = {}
+          const objId = self.extractObjId(searchQuery)
+          const results = {}
           let isEmptyResults = true
-          let categories = [
+          const categories = [
             {
               code: 'federation',
-              name: self.$pgettext('*/*/*', 'Federation'),
+              name: self.$pgettext('*/*/*', 'Federation')
             },
             {
               code: 'podcasts',
-              name: self.$pgettext('*/*/*', 'Podcasts'),
+              name: self.$pgettext('*/*/*', 'Podcasts')
             },
             {
               code: 'artists',
@@ -148,12 +155,12 @@ export default {
               },
               getId (t) {
                 return t.name
-              },
+              }
             },
             {
               code: 'more',
-              name: '',
-            },
+              name: ''
+            }
           ]
           categories.forEach(category => {
             results[category.code] = {
@@ -161,29 +168,27 @@ export default {
               results: []
             }
             if (category.code === 'federation') {
-
               if (objId) {
                 isEmptyResults = false
-                let searchMessage = self.$pgettext('Search/*/*', 'Search on the fediverse')
-                results['federation'] = {
+                const searchMessage = self.$pgettext('Search/*/*', 'Search on the fediverse')
+                results.federation = {
                   name: self.$pgettext('*/*/*', 'Federation'),
                   results: [{
                     title: searchMessage,
                     routerUrl: {
                       name: 'search',
                       query: {
-                        id: objId,
+                        id: objId
                       }
                     }
                   }]
                 }
               }
-            }
-            else if (category.code === 'podcasts') {
+            } else if (category.code === 'podcasts') {
               if (objId) {
                 isEmptyResults = false
-                let searchMessage = self.$pgettext('Search/*/*', 'Subscribe to podcast via RSS')
-                results['podcasts'] = {
+                const searchMessage = self.$pgettext('Search/*/*', 'Subscribe to podcast via RSS')
+                results.podcasts = {
                   name: self.$pgettext('*/*/*', 'Podcasts'),
                   results: [{
                     title: searchMessage,
@@ -191,33 +196,31 @@ export default {
                       name: 'search',
                       query: {
                         id: objId,
-                        type: "rss"
+                        type: 'rss'
                       }
                     }
                   }]
                 }
               }
-            }
-            else if (category.code === 'more') {
-              let searchMessage = self.$pgettext('Search/*/*', 'More results 🡒')
-              results['more'] = {
+            } else if (category.code === 'more') {
+              const searchMessage = self.$pgettext('Search/*/*', 'More results 🡒')
+              results.more = {
                 name: '',
                 results: [{
                   title: searchMessage,
                   routerUrl: {
                     name: 'search',
                     query: {
-                      type: "artists",
+                      type: 'artists',
                       q: searchQuery
                     }
                   }
                 }]
               }
-            }
-            else {
+            } else {
               initialResponse[category.code].forEach(result => {
                 isEmptyResults = false
-                let id = category.getId(result)
+                const id = category.getId(result)
                 results[category.code].results.push({
                   title: category.getTitle(result),
                   id,
diff --git a/front/src/components/audio/VolumeControl.vue b/front/src/components/audio/VolumeControl.vue
index 06110a4e2..aa61c89b8 100644
--- a/front/src/components/audio/VolumeControl.vue
+++ b/front/src/components/audio/VolumeControl.vue
@@ -1,74 +1,87 @@
 <template>
-   <button class="circular control button" :class="['component-volume-control', {'expanded': expanded}]" @click.prevent.stop="" @mouseover="handleOver" @mouseleave="handleLeave">
+  <button
+    class="circular control button"
+    :class="['component-volume-control', {'expanded': expanded}]"
+    @click.prevent.stop=""
+    @mouseover="handleOver"
+    @mouseleave="handleLeave"
+  >
     <span
-      role="button"
       v-if="sliderVolume === 0"
+      role="button"
       :title="labels.unmute"
       :aria-label="labels.unmute"
-      @click.prevent.stop="unmute">
-      <i class="volume off icon"></i>
+      @click.prevent.stop="unmute"
+    >
+      <i class="volume off icon" />
     </span>
     <span
-      role="button"
       v-else-if="sliderVolume < 0.5"
+      role="button"
       :title="labels.mute"
       :aria-label="labels.mute"
-      @click.prevent.stop="mute">
-      <i class="volume down icon"></i>
+      @click.prevent.stop="mute"
+    >
+      <i class="volume down icon" />
     </span>
     <span
-      role="button"
       v-else
+      role="button"
       :title="labels.mute"
       :aria-label="labels.mute"
-      @click.prevent.stop="mute">
-      <i class="volume up icon"></i>
+      @click.prevent.stop="mute"
+    >
+      <i class="volume up icon" />
     </span>
     <div class="popup">
-      <label for="volume-slider" class="visually-hidden">{{ labels.slider }}</label>
+      <label
+        for="volume-slider"
+        class="visually-hidden"
+      >{{ labels.slider }}</label>
       <input
         id="volume-slider"
+        v-model="sliderVolume"
         type="range"
         step="any"
         min="0"
-	 v-bind:max="volumeSteps"
-        v-model="sliderVolume" />
+        :max="volumeSteps"
+      >
     </div>
   </button>
 </template>
 <script>
-import { mapState, mapGetters, mapActions } from "vuex"
+import mapActions from 'vuex'
 
 export default {
   data () {
     return {
       expanded: false,
       timeout: null,
-      volumeSteps: 100,
+      volumeSteps: 100
     }
   },
   computed: {
     sliderVolume: {
       get () {
-        return this.$store.state.player.volume * this.volumeSteps;
+        return this.$store.state.player.volume * this.volumeSteps
       },
       set (v) {
-        this.$store.commit("player/volume", v / this.volumeSteps)
+        this.$store.commit('player/volume', v / this.volumeSteps)
       }
     },
     labels () {
       return {
-        unmute: this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Unmute"),
-        mute: this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Mute"),
-        slider: this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', "Adjust volume")
+        unmute: this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Unmute'),
+        mute: this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Mute'),
+        slider: this.$pgettext('Sidebar/Player/Icon.Tooltip/Verb', 'Adjust volume')
       }
     }
   },
   methods: {
     ...mapActions({
-      mute: "player/mute",
-      unmute: "player/unmute",
-      toggleMute: "player/toggleMute",
+      mute: 'player/mute',
+      unmute: 'player/unmute',
+      toggleMute: 'player/toggleMute'
     }),
     handleOver () {
       if (this.timeout) {
@@ -80,7 +93,7 @@ export default {
       if (this.timeout) {
         clearTimeout(this.timeout)
       }
-      this.timeout = setTimeout(() => {this.expanded = false}, 500)
+      this.timeout = setTimeout(() => { this.expanded = false }, 500)
     }
   }
 }
diff --git a/front/src/components/audio/album/Card.vue b/front/src/components/audio/album/Card.vue
index cba58c9dd..7f5afac63 100644
--- a/front/src/components/audio/album/Card.vue
+++ b/front/src/components/audio/album/Card.vue
@@ -1,19 +1,32 @@
 <template>
   <div class="card app-card component-album-card">
     <div
+      v-lazy:background-image="imageUrl"
+      :class="['ui', 'head-image', 'image', {'default-cover': !album.cover || !album.cover.urls.original}]"
       @click="$router.push({name: 'library.albums.detail', params: {id: album.id}})"
-      :class="['ui', 'head-image', 'image', {'default-cover': !album.cover || !album.cover.urls.original}]" v-lazy:background-image="imageUrl">
-      <play-button :icon-only="true" :is-playable="album.is_playable" :button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']" :album="album"></play-button>
+    >
+      <play-button
+        :icon-only="true"
+        :is-playable="album.is_playable"
+        :button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']"
+        :album="album"
+      />
     </div>
     <div class="content">
       <strong>
-        <router-link class="discrete link" :to="{name: 'library.albums.detail', params: {id: album.id}}">
+        <router-link
+          class="discrete link"
+          :to="{name: 'library.albums.detail', params: {id: album.id}}"
+        >
           {{ album.title }}
         </router-link>
       </strong>
       <div class="description">
         <span>
-          <router-link class="discrete link" :to="{name: 'library.artists.detail', params: {id: album.artist.id}}">
+          <router-link
+            class="discrete link"
+            :to="{name: 'library.artists.detail', params: {id: album.artist.id}}"
+          >
             {{ album.artist.name }}
           </router-link>
         </span>
@@ -21,8 +34,21 @@
     </div>
     <div class="extra content">
       <span v-if="album.release_date">{{ album.release_date | moment('Y') }} · </span>
-      <translate translate-context="*/*/*" :translate-params="{count: album.tracks_count}" :translate-n="album.tracks_count" translate-plural="%{ count } tracks">%{ count } track</translate>
-      <play-button class="right floated basic icon" :dropdown-only="true" :is-playable="album.is_playable" :dropdown-icon-classes="['ellipsis', 'horizontal', 'large really discrete']" :album="album"></play-button>
+      <translate
+        translate-context="*/*/*"
+        :translate-params="{count: album.tracks_count}"
+        :translate-n="album.tracks_count"
+        translate-plural="%{ count } tracks"
+      >
+        %{ count } track
+      </translate>
+      <play-button
+        class="right floated basic icon"
+        :dropdown-only="true"
+        :is-playable="album.is_playable"
+        :dropdown-icon-classes="['ellipsis', 'horizontal', 'large really discrete']"
+        :album="album"
+      />
     </div>
   </div>
 </template>
@@ -31,17 +57,18 @@
 import PlayButton from '@/components/audio/PlayButton'
 
 export default {
-  props: {
-    album: {type: Object},
-  },
   components: {
     PlayButton
   },
+  props: {
+    album: { type: Object, required: true }
+  },
   computed: {
     imageUrl () {
       if (this.album.cover && this.album.cover.urls.original) {
         return this.$store.getters['instance/absoluteUrl'](this.album.cover.urls.medium_square_crop)
       }
+      return null
     }
   }
 }
diff --git a/front/src/components/audio/album/Widget.vue b/front/src/components/audio/album/Widget.vue
index 6323ab87b..44a59473c 100644
--- a/front/src/components/audio/album/Widget.vue
+++ b/front/src/components/audio/album/Widget.vue
@@ -1,25 +1,54 @@
 <template>
   <div class="wrapper">
-    <h3 v-if="!!this.$slots.title" class="ui header">
-      <slot name="title"></slot>
-      <span v-if="showCount" class="ui tiny circular label">{{ count }}</span>
+    <h3
+      v-if="!!$slots.title"
+      class="ui header"
+    >
+      <slot name="title" />
+      <span
+        v-if="showCount"
+        class="ui tiny circular label"
+      >{{ count }}</span>
     </h3>
-    <slot></slot>
-    <inline-search-bar v-model="query" v-if="search" @search="albums = []; fetchData()"></inline-search-bar>
-    <div class="ui hidden divider"></div>
+    <slot />
+    <inline-search-bar
+      v-if="search"
+      v-model="query"
+      @search="albums = []; fetchData()"
+    />
+    <div class="ui hidden divider" />
     <div class="ui app-cards cards">
-      <div v-if="isLoading" class="ui inverted active dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui inverted active dimmer"
+      >
+        <div class="ui loader" />
       </div>
-      <album-card v-for="album in albums" :album="album" :key="album.id" />
+      <album-card
+        v-for="album in albums"
+        :key="album.id"
+        :album="album"
+      />
     </div>
-    <slot v-if="!isLoading && albums.length === 0" name="empty-state">
-      <empty-state @refresh="fetchData" :refresh="true"></empty-state>
+    <slot
+      v-if="!isLoading && albums.length === 0"
+      name="empty-state"
+    >
+      <empty-state
+        :refresh="true"
+        @refresh="fetchData"
+      />
     </slot>
     <template v-if="nextPage">
-      <div class="ui hidden divider"></div>
-      <button v-if="nextPage" @click="fetchData(nextPage)" :class="['ui', 'basic', 'button']">
-        <translate translate-context="*/*/Button,Label">Show more</translate>
+      <div class="ui hidden divider" />
+      <button
+        v-if="nextPage"
+        :class="['ui', 'basic', 'button']"
+        @click="fetchData(nextPage)"
+      >
+        <translate translate-context="*/*/Button,Label">
+          Show more
+        </translate>
       </button>
     </template>
   </div>
@@ -30,16 +59,16 @@ import axios from 'axios'
 import AlbumCard from '@/components/audio/album/Card'
 
 export default {
-  props: {
-    filters: {type: Object, required: true},
-    controls: {type: Boolean, default: true},
-    showCount: {type: Boolean, default: false},
-    search: {type: Boolean, default: false},
-    limit: {type: Number, default: 12},
-  },
   components: {
     AlbumCard
   },
+  props: {
+    filters: { type: Object, required: true },
+    controls: { type: Boolean, default: true },
+    showCount: { type: Boolean, default: false },
+    search: { type: Boolean, default: false },
+    limit: { type: Number, default: 12 }
+  },
   data () {
     return {
       albums: [],
@@ -48,7 +77,15 @@ export default {
       errors: null,
       previousPage: null,
       nextPage: null,
-      query: '',
+      query: ''
+    }
+  },
+  watch: {
+    offset () {
+      this.fetchData()
+    },
+    '$store.state.moderation.lastUpdate': function () {
+      this.fetchData()
     }
   },
   created () {
@@ -58,11 +95,11 @@ export default {
     fetchData (url) {
       url = url || 'albums/'
       this.isLoading = true
-      let self = this
-      let params = {q: this.query, ...this.filters}
+      const self = this
+      const params = { q: this.query, ...this.filters }
       params.page_size = this.limit
       params.offset = this.offset
-      axios.get(url, {params: params}).then((response) => {
+      axios.get(url, { params: params }).then((response) => {
         self.previousPage = response.data.previous
         self.nextPage = response.data.next
         self.isLoading = false
@@ -79,14 +116,6 @@ export default {
       } else {
         this.offset = Math.max(this.offset - this.limit, 0)
       }
-    },
-  },
-  watch: {
-    offset () {
-      this.fetchData()
-    },
-    "$store.state.moderation.lastUpdate": function () {
-      this.fetchData()
     }
   }
 }
diff --git a/front/src/components/audio/artist/Card.vue b/front/src/components/audio/artist/Card.vue
index f51c48bd6..c02d825f7 100644
--- a/front/src/components/audio/artist/Card.vue
+++ b/front/src/components/audio/artist/Card.vue
@@ -1,49 +1,88 @@
 <template>
   <div class="app-card card">
     <div
+      v-lazy:background-image="imageUrl"
+      :class="['ui', 'head-image', 'circular', 'image', {'default-cover': !cover || !cover.urls.original}]"
       @click="$router.push({name: 'library.artists.detail', params: {id: artist.id}})"
-      :class="['ui', 'head-image', 'circular', 'image', {'default-cover': !cover || !cover.urls.original}]" v-lazy:background-image="imageUrl">
-      <play-button :icon-only="true" :is-playable="artist.is_playable" :button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']" :artist="artist"></play-button>
+    >
+      <play-button
+        :icon-only="true"
+        :is-playable="artist.is_playable"
+        :button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']"
+        :artist="artist"
+      />
     </div>
     <div class="content">
       <strong>
-        <router-link class="discrete link" :to="{name: 'library.artists.detail', params: {id: artist.id}}">
+        <router-link
+          class="discrete link"
+          :to="{name: 'library.artists.detail', params: {id: artist.id}}"
+        >
           {{ artist.name|truncate(30) }}
         </router-link>
       </strong>
 
-      <tags-list label-classes="tiny" :truncate-size="20" :limit="2" :show-more="false" :tags="artist.tags"></tags-list>
+      <tags-list
+        label-classes="tiny"
+        :truncate-size="20"
+        :limit="2"
+        :show-more="false"
+        :tags="artist.tags"
+      />
     </div>
     <div class="extra content">
-      <translate v-if="artist.content_category === 'music'" translate-context="*/*/*" :translate-params="{count: artist.tracks_count}" :translate-n="artist.tracks_count" translate-plural="%{ count } tracks">%{ count } track</translate>
-      <translate v-else translate-context="*/*/*" :translate-params="{count: artist.tracks_count}" :translate-n="artist.tracks_count" translate-plural="%{ count } episodes">%{ count } episode</translate>      
-      <play-button class="right floated basic icon" :dropdown-only="true" :is-playable="artist.is_playable" :dropdown-icon-classes="['ellipsis', 'horizontal', 'large really discrete']" :artist="artist"></play-button>
+      <translate
+        v-if="artist.content_category === 'music'"
+        translate-context="*/*/*"
+        :translate-params="{count: artist.tracks_count}"
+        :translate-n="artist.tracks_count"
+        translate-plural="%{ count } tracks"
+      >
+        %{ count } track
+      </translate>
+      <translate
+        v-else
+        translate-context="*/*/*"
+        :translate-params="{count: artist.tracks_count}"
+        :translate-n="artist.tracks_count"
+        translate-plural="%{ count } episodes"
+      >
+        %{ count } episode
+      </translate>
+      <play-button
+        class="right floated basic icon"
+        :dropdown-only="true"
+        :is-playable="artist.is_playable"
+        :dropdown-icon-classes="['ellipsis', 'horizontal', 'large really discrete']"
+        :artist="artist"
+      />
     </div>
   </div>
 </template>
 
 <script>
 import PlayButton from '@/components/audio/PlayButton'
-import TagsList from "@/components/tags/List"
+import TagsList from '@/components/tags/List'
 
 export default {
-  props: ['artist'],
   components: {
     PlayButton,
     TagsList
   },
+  props: { artist: { type: Object, required: true } },
   data () {
     return {
       initialAlbums: 30,
-      showAllAlbums: true,
+      showAllAlbums: true
     }
   },
   computed: {
     imageUrl () {
-      let cover = this.cover
+      const cover = this.cover
       if (cover && cover.urls.original) {
         return this.$store.getters['instance/absoluteUrl'](cover.urls.medium_square_crop)
       }
+      return null
     },
     cover () {
       if (this.artist.cover && this.artist.cover.urls.original) {
@@ -54,7 +93,7 @@ export default {
       }).filter((c) => {
         return c && c.urls.original
       })[0]
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/audio/artist/Widget.vue b/front/src/components/audio/artist/Widget.vue
index 5ea2e1607..9cf3ae0cc 100644
--- a/front/src/components/audio/artist/Widget.vue
+++ b/front/src/components/audio/artist/Widget.vue
@@ -1,24 +1,50 @@
 <template>
   <div class="wrapper">
-    <h3 v-if="header" class="ui header">
-      <slot name="title"></slot>
+    <h3
+      v-if="header"
+      class="ui header"
+    >
+      <slot name="title" />
       <span class="ui tiny circular label">{{ count }}</span>
     </h3>
-    <inline-search-bar v-model="query" v-if="search" @search="objects = []; fetchData()"></inline-search-bar>
-    <div class="ui hidden divider"></div>
+    <inline-search-bar
+      v-if="search"
+      v-model="query"
+      @search="objects = []; fetchData()"
+    />
+    <div class="ui hidden divider" />
     <div class="ui five app-cards cards">
-      <div v-if="isLoading" class="ui inverted active dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui inverted active dimmer"
+      >
+        <div class="ui loader" />
       </div>
-      <artist-card :artist="artist" v-for="artist in objects" :key="artist.id"></artist-card>
+      <artist-card
+        v-for="artist in objects"
+        :key="artist.id"
+        :artist="artist"
+      />
     </div>
-    <slot v-if="!isLoading && objects.length === 0" name="empty-state">
-      <empty-state @refresh="fetchData" :refresh="true"></empty-state>
+    <slot
+      v-if="!isLoading && objects.length === 0"
+      name="empty-state"
+    >
+      <empty-state
+        :refresh="true"
+        @refresh="fetchData"
+      />
     </slot>
     <template v-if="nextPage">
-      <div class="ui hidden divider"></div>
-      <button v-if="nextPage" @click="fetchData(nextPage)" :class="['ui', 'basic', 'button']">
-        <translate translate-context="*/*/Button,Label">Show more</translate>
+      <div class="ui hidden divider" />
+      <button
+        v-if="nextPage"
+        :class="['ui', 'basic', 'button']"
+        @click="fetchData(nextPage)"
+      >
+        <translate translate-context="*/*/Button,Label">
+          Show more
+        </translate>
       </button>
     </template>
   </div>
@@ -26,17 +52,17 @@
 
 <script>
 import axios from 'axios'
-import ArtistCard from "@/components/audio/artist/Card"
+import ArtistCard from '@/components/audio/artist/Card'
 
 export default {
-  props: {
-    filters: {type: Object, required: true},
-    controls: {type: Boolean, default: true},
-    header: {type: Boolean, default: true},
-    search: {type: Boolean, default: false},
-  },
   components: {
-    ArtistCard,
+    ArtistCard
+  },
+  props: {
+    filters: { type: Object, required: true },
+    controls: { type: Boolean, default: true },
+    header: { type: Boolean, default: true },
+    search: { type: Boolean, default: false }
   },
   data () {
     return {
@@ -47,7 +73,15 @@ export default {
       errors: null,
       previousPage: null,
       nextPage: null,
-      query: '',
+      query: ''
+    }
+  },
+  watch: {
+    offset () {
+      this.fetchData()
+    },
+    '$store.state.moderation.lastUpdate': function () {
+      this.fetchData()
     }
   },
   created () {
@@ -57,11 +91,11 @@ export default {
     fetchData (url) {
       url = url || 'artists/'
       this.isLoading = true
-      let self = this
-      let params = {q: this.query, ...this.filters}
+      const self = this
+      const params = { q: this.query, ...this.filters }
       params.page_size = this.limit
       params.offset = this.offset
-      axios.get(url, {params: params}).then((response) => {
+      axios.get(url, { params: params }).then((response) => {
         self.previousPage = response.data.previous
         self.nextPage = response.data.next
         self.isLoading = false
@@ -78,14 +112,6 @@ export default {
       } else {
         this.offset = Math.max(this.offset - this.limit, 0)
       }
-    },
-  },
-  watch: {
-    offset () {
-      this.fetchData()
-    },
-    "$store.state.moderation.lastUpdate": function () {
-      this.fetchData()
     }
   }
 }
diff --git a/front/src/components/audio/podcast/MobileRow.vue b/front/src/components/audio/podcast/MobileRow.vue
index 9e7236e7a..e4f0db980 100644
--- a/front/src/components/audio/podcast/MobileRow.vue
+++ b/front/src/components/audio/podcast/MobileRow.vue
@@ -7,12 +7,10 @@
   >
     <div
       v-if="showArt"
-      @click.prevent.exact="activateTrack(track, index)"
       class="image left floated column"
+      @click.prevent.exact="activateTrack(track, index)"
     >
       <img
-        alt=""
-        class="ui artist-track mini image"
         v-if="
           track.album && track.album.cover && track.album.cover.urls.original
         "
@@ -21,10 +19,10 @@
             track.album.cover.urls.medium_square_crop
           )
         "
-      />
-      <img
         alt=""
         class="ui artist-track mini image"
+      >
+      <img
         v-else-if="
           track.cover
         "
@@ -33,10 +31,10 @@
             track.cover.urls.medium_square_crop
           )
         "
-      />
-      <img
         alt=""
         class="ui artist-track mini image"
+      >
+      <img
         v-else-if="
           track.artist.cover
         "
@@ -45,19 +43,21 @@
             track.artist.cover.urls.medium_square_crop
           )
         "
-      />
-      <img
         alt=""
         class="ui artist-track mini image"
+      >
+      <img
         v-else
+        alt=""
+        class="ui artist-track mini image"
         src="../../../assets/audio/default-cover.png"
-      />
+      >
     </div>
     <div
-      tabindex=0
-      @click="activateTrack(track, index)"
+      tabindex="0"
       role="button"
       class="content ellipsis left floated column"
+      @click="activateTrack(track, index)"
     >
       <p
         :class="[
@@ -68,24 +68,33 @@
       >
         {{ track.title }}
       </p>
-      <p v-if="track.artist.content_category === 'podcast'" class="track-meta mobile">
-        <human-date class="really discrete" :date="track.creation_date"></human-date>
+      <p
+        v-if="track.artist.content_category === 'podcast'"
+        class="track-meta mobile"
+      >
+        <human-date
+          class="really discrete"
+          :date="track.creation_date"
+        />
         <span>&#183;</span>
         <human-duration
           v-if="track.uploads[0] && track.uploads[0].duration"
           :duration="track.uploads[0].duration"
-        ></human-duration>
+        />
       </p>
-      <p v-else class="track-meta mobile">
+      <p
+        v-else
+        class="track-meta mobile"
+      >
         {{ track.artist.name }} <span>&#183;</span>
         <human-duration
           v-if="track.uploads[0] && track.uploads[0].duration"
           :duration="track.uploads[0].duration"
-        ></human-duration>
+        />
       </p>
     </div>
     <div
-      v-if="$store.state.auth.authenticated && this.track.artist.content_category !== 'podcast'"
+      v-if="$store.state.auth.authenticated && track.artist.content_category !== 'podcast'"
       :class="[
         'meta',
         'right',
@@ -100,12 +109,11 @@
         class="tiny"
         :border="false"
         :track="track"
-      ></track-favorite-icon>
+      />
     </div>
     <div
       role="button"
       :aria-label="actionsButtonLabel"
-      @click.prevent.exact="showTrackModal = !showTrackModal"
       :class="[
         'modal-button',
         'right',
@@ -114,36 +122,36 @@
         'mobile',
         { 'with-art': showArt },
       ]"
+      @click.prevent.exact="showTrackModal = !showTrackModal"
     >
       <i class="ellipsis large vertical icon" />
     </div>
     <track-modal
-      @update:show="showTrackModal = $event;"
       :show="showTrackModal"
       :track="track"
       :index="index"
       :is-artist="isArtist"
       :is-album="isAlbum"
-    ></track-modal>
+      @update:show="showTrackModal = $event;"
+    />
   </div>
 </template>
 
 <script>
-import PlayIndicator from "@/components/audio/track/PlayIndicator";
-import { mapActions, mapGetters } from "vuex";
-import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon";
-import TrackModal from "@/components/audio/track/Modal";
-import PlayOptionsMixin from "@/components/mixins/PlayOptions"
+import { mapActions, mapGetters } from 'vuex'
+import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
+import TrackModal from '@/components/audio/track/Modal'
+import PlayOptionsMixin from '@/components/mixins/PlayOptions'
 
 export default {
-  mixins: [PlayOptionsMixin],
-  data() {
-    return {
-      showTrackModal: false,
-    }
+
+  components: {
+    TrackFavoriteIcon,
+    TrackModal
   },
+  mixins: [PlayOptionsMixin],
   props: {
-    tracks: Array,
+    tracks: { type: Array, required: true },
     showAlbum: { type: Boolean, required: false, default: true },
     showArtist: { type: Boolean, required: false, default: true },
     showPosition: { type: Boolean, required: false, default: false },
@@ -155,41 +163,40 @@ export default {
     showDuration: { type: Boolean, required: false, default: true },
     index: { type: Number, required: true },
     track: { type: Object, required: true },
-    isArtist: {type: Boolean, required: false, default: false},
-    isAlbum: {type: Boolean, required: false, default: false},
+    isArtist: { type: Boolean, required: false, default: false },
+    isAlbum: { type: Boolean, required: false, default: false }
   },
-
-  components: {
-    PlayIndicator,
-    TrackFavoriteIcon,
-    TrackModal,
+  data () {
+    return {
+      showTrackModal: false
+    }
   },
   computed: {
     ...mapGetters({
-      currentTrack: "queue/currentTrack",
+      currentTrack: 'queue/currentTrack'
     }),
 
-    isPlaying() {
-      return this.$store.state.player.playing;
+    isPlaying () {
+      return this.$store.state.player.playing
     },
     actionsButtonLabel () {
-        return this.$pgettext('Content/Track/Icon.Tooltip/Verb', 'Show track actions')
-    },
+      return this.$pgettext('Content/Track/Icon.Tooltip/Verb', 'Show track actions')
+    }
   },
 
   methods: {
-    prettyPosition(position, size) {
-      var s = String(position);
+    prettyPosition (position, size) {
+      let s = String(position)
       while (s.length < (size || 2)) {
-        s = "0" + s;
+        s = '0' + s
       }
-      return s;
+      return s
     },
 
     ...mapActions({
-      resumePlayback: "player/resumePlayback",
-      pausePlayback: "player/pausePlayback",
-    }),
-  },
-};
+      resumePlayback: 'player/resumePlayback',
+      pausePlayback: 'player/pausePlayback'
+    })
+  }
+}
 </script>
diff --git a/front/src/components/audio/podcast/Row.vue b/front/src/components/audio/podcast/Row.vue
index 4339166f5..17fcb30a2 100644
--- a/front/src/components/audio/podcast/Row.vue
+++ b/front/src/components/audio/podcast/Row.vue
@@ -15,8 +15,6 @@
       @click.prevent.exact="activateTrack(track, index)"
     >
       <img
-        alt=""
-        class="ui artist-track mini image"
         v-if="
           track.cover && track.cover.urls.original
         "
@@ -25,10 +23,10 @@
             track.cover.urls.medium_square_crop
           )
         "
-      />
-      <img
         alt=""
         class="ui artist-track mini image"
+      >
+      <img
         v-else-if="
           defaultCover
         "
@@ -37,21 +35,32 @@
             defaultCover.cover.urls.medium_square_crop
           )
         "
-      />
-      <img
         alt=""
         class="ui artist-track mini image"
+      >
+      <img
         v-else
+        alt=""
+        class="ui artist-track mini image"
         src="../../../assets/audio/default-cover.png"
-      />
+      >
     </div>
-    <div tabindex=0 class="content left floated column">
+    <div
+      tabindex="0"
+      class="content left floated column"
+    >
       <a
         class="podcast-episode-title ellipsis"
-        @click.prevent.exact="activateTrack(track, index)">{{ track.title }}</a>
-      <p class="podcast-episode-meta">{{ description.text }}</p>
+        @click.prevent.exact="activateTrack(track, index)"
+      >{{ track.title }}</a>
+      <p class="podcast-episode-meta">
+        {{ description.text }}
+      </p>
     </div>
-    <div v-if="displayActions" class="meta right floated column">
+    <div
+      v-if="displayActions"
+      class="meta right floated column"
+    >
       <play-button
         id="playmenu"
         class="play-button basic icon"
@@ -63,22 +72,25 @@
           'large really discrete',
         ]"
         :track="track"
-      ></play-button>
+      />
     </div>
   </div>
 </template>
 
 <script>
 import axios from 'axios'
-import PlayIndicator from "@/components/audio/track/PlayIndicator";
-import { mapActions, mapGetters } from "vuex";
-import PlayButton from "@/components/audio/PlayButton";
-import PlayOptions from "@/components/mixins/PlayOptions";
+import { mapActions, mapGetters } from 'vuex'
+import PlayButton from '@/components/audio/PlayButton'
+import PlayOptions from '@/components/mixins/PlayOptions'
 
 export default {
+
+  components: {
+    PlayButton
+  },
   mixins: [PlayOptions],
   props: {
-    tracks: Array,
+    tracks: { type: Array, required: true },
     showAlbum: { type: Boolean, required: false, default: true },
     showArtist: { type: Boolean, required: false, default: true },
     showPosition: { type: Boolean, required: false, default: false },
@@ -90,34 +102,29 @@ export default {
     showDuration: { type: Boolean, required: false, default: true },
     index: { type: Number, required: true },
     track: { type: Object, required: true },
-    defaultCover: { type: Object, required: false },
+    defaultCover: { type: Object, required: false, default: () => { return {} } }
   },
 
-  data() {
+  data () {
     return {
       hover: null,
       errors: null,
-      description: null,
+      description: null
     }
   },
 
-  created () {
-    this.fetchData('tracks/' + this.track.id + '/' )
-	},
-
-  components: {
-    PlayIndicator,
-    PlayButton,
-  },
-
   computed: {
     ...mapGetters({
-      currentTrack: "queue/currentTrack",
+      currentTrack: 'queue/currentTrack'
     }),
 
-    isPlaying() {
-      return this.$store.state.player.playing;
-    },
+    isPlaying () {
+      return this.$store.state.player.playing
+    }
+  },
+
+  created () {
+    this.fetchData('tracks/' + this.track.id + '/')
   },
 
   methods: {
@@ -126,29 +133,29 @@ export default {
         return
       }
       this.isLoading = true
-      let self = this
+      const self = this
       try {
-        let channelsPromise = await axios.get(url)
+        const channelsPromise = await axios.get(url)
         self.description = channelsPromise.data.description
         self.isLoading = false
-      } catch(e) {
+      } catch (e) {
         self.isLoading = false
-        self.errors = error.backendErrors
+        self.errors = e.backendErrors
       }
     },
 
-    prettyPosition(position, size) {
-      var s = String(position);
+    prettyPosition (position, size) {
+      let s = String(position)
       while (s.length < (size || 2)) {
-        s = "0" + s;
+        s = '0' + s
       }
-      return s;
+      return s
     },
 
     ...mapActions({
-      resumePlayback: "player/resumePlayback",
-      pausePlayback: "player/pausePlayback",
-    }),
-  },
-};
+      resumePlayback: 'player/resumePlayback',
+      pausePlayback: 'player/pausePlayback'
+    })
+  }
+}
 </script>
diff --git a/front/src/components/audio/podcast/Table.vue b/front/src/components/audio/podcast/Table.vue
index 44f01c820..2f03f1f97 100644
--- a/front/src/components/audio/podcast/Table.vue
+++ b/front/src/components/audio/podcast/Table.vue
@@ -1,10 +1,10 @@
 <template>
   <div>
-    <div class="ui hidden divider"></div>
+    <div class="ui hidden divider" />
 
     <!-- Add a header if needed -->
 
-    <slot name="header"></slot>
+    <slot name="header" />
 
     <div>
       <div
@@ -13,38 +13,44 @@
         <!-- For each item, build a row -->
         <podcast-row
           v-for="(track, index) in tracks"
-          :track="track"
           :key="track.id"
+          :track="track"
           :index="index"
           :tracks="tracks"
           :display-actions="displayActions"
           :show-duration="showDuration"
           :is-podcast="isPodcast"
-        ></podcast-row>
+        />
       </div>
-      <div v-if="paginateResults" class="ui center aligned basic segment desktop-and-up">
+      <div
+        v-if="paginateResults"
+        class="ui center aligned basic segment desktop-and-up"
+      >
         <pagination
           :total="total"
           :current="page"
           :paginate-by="paginateBy"
-          v-on="$listeners">
-        </pagination>
+          v-on="$listeners"
+        />
       </div>
     </div>
 
     <div
       :class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-below']"
     >
-      <div v-if="isLoading" class="ui inverted active dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui inverted active dimmer"
+      >
+        <div class="ui loader" />
       </div>
 
       <!-- For each item, build a row -->
 
       <track-mobile-row
         v-for="(track, index) in tracks"
-        :track="track"
         :key="track.id"
+        :track="track"
         :index="index"
         :tracks="tracks"
         :show-position="showPosition"
@@ -53,36 +59,37 @@
         :is-artist="isArtist"
         :is-album="isAlbum"
         :is-podcast="isPodcast"
-      ></track-mobile-row>
-      <div v-if="paginateResults" class="ui center aligned basic segment tablet-and-below">
+      />
+      <div
+        v-if="paginateResults"
+        class="ui center aligned basic segment tablet-and-below"
+      >
         <pagination
           v-if="paginateResults"
           :total="total"
           :current="page"
           :compact="true"
-          v-on="$listeners"></pagination>
+          v-on="$listeners"
+        />
       </div>
     </div>
   </div>
 </template>
 
 <script>
-import _ from "@/lodash";
-import TrackRow from "@/components/audio/track/Row";
-import PodcastRow from "@/components/audio/podcast/Row";
-import TrackMobileRow from "@/components/audio/track/MobileRow";
-import Pagination from "@/components/Pagination";
+import PodcastRow from '@/components/audio/podcast/Row'
+import TrackMobileRow from '@/components/audio/track/MobileRow'
+import Pagination from '@/components/Pagination'
 
 export default {
   components: {
-    TrackRow,
     TrackMobileRow,
     Pagination,
-    PodcastRow,
+    PodcastRow
   },
 
   props: {
-    tracks: Array,
+    tracks: { type: Array, required: true },
     showAlbum: { type: Boolean, required: false, default: true },
     showArtist: { type: Boolean, required: false, default: true },
     showPosition: { type: Boolean, required: false, default: false },
@@ -94,33 +101,33 @@ export default {
     showDuration: { type: Boolean, required: false, default: true },
     isArtist: { type: Boolean, required: false, default: false },
     isAlbum: { type: Boolean, required: false, default: false },
-    paginateResults: { type: Boolean, required: false, default: true},
-    total: { type: Number, required: false},
-    page: {type: Number, required: false, default: 1},
-    paginateBy: {type: Number, required: false, default: 25},
-    isPodcast: {type: Boolean, required: true},
-    defaultCover: {type: Object, required: false},
+    paginateResults: { type: Boolean, required: false, default: true },
+    total: { type: Number, required: false, default: 0 },
+    page: { type: Number, required: false, default: 1 },
+    paginateBy: { type: Number, required: false, default: 25 },
+    isPodcast: { type: Boolean, required: true },
+    defaultCover: { type: Object, required: false, default: () => { return {} } }
   },
 
-  data() {
+  data () {
     return {
-      isLoading: false,
-    };
+      isLoading: false
+    }
   },
 
   computed: {
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext("*/*/*/Noun", "Title"),
-        album: this.$pgettext("*/*/*/Noun", "Album"),
-        artist: this.$pgettext("*/*/*/Noun", "Artist"),
-      };
-    },
+        title: this.$pgettext('*/*/*/Noun', 'Title'),
+        album: this.$pgettext('*/*/*/Noun', 'Album'),
+        artist: this.$pgettext('*/*/*/Noun', 'Artist')
+      }
+    }
   },
   methods: {
-    updatePage: function(page) {
+    updatePage: function (page) {
       this.$emit('page-changed', page)
     }
-  },
-};
+  }
+}
 </script>
diff --git a/front/src/components/audio/track/MobileRow.vue b/front/src/components/audio/track/MobileRow.vue
index da2b1f3d2..98005ff9d 100644
--- a/front/src/components/audio/track/MobileRow.vue
+++ b/front/src/components/audio/track/MobileRow.vue
@@ -7,12 +7,10 @@
   >
     <div
       v-if="showArt"
-      @click.prevent.exact="activateTrack(track, index)"
       class="image left floated column"
+      @click.prevent.exact="activateTrack(track, index)"
     >
       <img
-        alt=""
-        class="ui artist-track mini image"
         v-if="
           track.album && track.album.cover && track.album.cover.urls.original
         "
@@ -21,10 +19,10 @@
             track.album.cover.urls.medium_square_crop
           )
         "
-      />
-      <img
         alt=""
         class="ui artist-track mini image"
+      >
+      <img
         v-else-if="
           track.cover
         "
@@ -33,10 +31,10 @@
             track.cover.urls.medium_square_crop
           )
         "
-      />
-      <img
         alt=""
         class="ui artist-track mini image"
+      >
+      <img
         v-else-if="
           track.artist.cover
         "
@@ -45,19 +43,21 @@
             track.artist.cover.urls.medium_square_crop
           )
         "
-      />
-      <img
         alt=""
         class="ui artist-track mini image"
+      >
+      <img
         v-else
+        alt=""
+        class="ui artist-track mini image"
         src="../../../assets/audio/default-cover.png"
-      />
+      >
     </div>
     <div
-      tabindex=0
-      @click="activateTrack(track, index)"
+      tabindex="0"
       role="button"
       class="content ellipsis left floated column"
+      @click="activateTrack(track, index)"
     >
       <p
         :class="[
@@ -73,7 +73,7 @@
         <human-duration
           v-if="track.uploads[0] && track.uploads[0].duration"
           :duration="track.uploads[0].duration"
-        ></human-duration>
+        />
       </p>
     </div>
     <div
@@ -92,12 +92,11 @@
         class="tiny"
         :border="false"
         :track="track"
-      ></track-favorite-icon>
+      />
     </div>
     <div
       role="button"
       :aria-label="actionsButtonLabel"
-      @click.prevent.exact="showTrackModal = !showTrackModal"
       :class="[
         'modal-button',
         'right',
@@ -106,36 +105,36 @@
         'mobile',
         { 'with-art': showArt },
       ]"
+      @click.prevent.exact="showTrackModal = !showTrackModal"
     >
       <i class="ellipsis large vertical icon" />
     </div>
     <track-modal
-      @update:show="showTrackModal = $event;"
       :show="showTrackModal"
       :track="track"
       :index="index"
       :is-artist="isArtist"
       :is-album="isAlbum"
-    ></track-modal>
+      @update:show="showTrackModal = $event;"
+    />
   </div>
 </template>
 
 <script>
-import PlayIndicator from "@/components/audio/track/PlayIndicator";
-import { mapActions, mapGetters } from "vuex";
-import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon";
-import TrackModal from "@/components/audio/track/Modal";
-import PlayOptionsMixin from "@/components/mixins/PlayOptions"
+import { mapActions, mapGetters } from 'vuex'
+import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
+import TrackModal from '@/components/audio/track/Modal'
+import PlayOptionsMixin from '@/components/mixins/PlayOptions'
 
 export default {
-  mixins: [PlayOptionsMixin],
-  data() {
-    return {
-      showTrackModal: false,
-    }
+
+  components: {
+    TrackFavoriteIcon,
+    TrackModal
   },
+  mixins: [PlayOptionsMixin],
   props: {
-    tracks: Array,
+    tracks: { type: Array, required: true },
     showAlbum: { type: Boolean, required: false, default: true },
     showArtist: { type: Boolean, required: false, default: true },
     showPosition: { type: Boolean, required: false, default: false },
@@ -147,41 +146,40 @@ export default {
     showDuration: { type: Boolean, required: false, default: true },
     index: { type: Number, required: true },
     track: { type: Object, required: true },
-    isArtist: {type: Boolean, required: false, default: false},
-    isAlbum: {type: Boolean, required: false, default: false},
+    isArtist: { type: Boolean, required: false, default: false },
+    isAlbum: { type: Boolean, required: false, default: false }
   },
-
-  components: {
-    PlayIndicator,
-    TrackFavoriteIcon,
-    TrackModal,
+  data () {
+    return {
+      showTrackModal: false
+    }
   },
   computed: {
     ...mapGetters({
-      currentTrack: "queue/currentTrack",
+      currentTrack: 'queue/currentTrack'
     }),
 
-    isPlaying() {
-      return this.$store.state.player.playing;
+    isPlaying () {
+      return this.$store.state.player.playing
     },
     actionsButtonLabel () {
-        return this.$pgettext('Content/Track/Icon.Tooltip/Verb', 'Show track actions')
-    },
+      return this.$pgettext('Content/Track/Icon.Tooltip/Verb', 'Show track actions')
+    }
   },
 
   methods: {
-    prettyPosition(position, size) {
-      var s = String(position);
+    prettyPosition (position, size) {
+      let s = String(position)
       while (s.length < (size || 2)) {
-        s = "0" + s;
+        s = '0' + s
       }
-      return s;
+      return s
     },
 
     ...mapActions({
-      resumePlayback: "player/resumePlayback",
-      pausePlayback: "player/pausePlayback",
-    }),
-  },
-};
+      resumePlayback: 'player/resumePlayback',
+      pausePlayback: 'player/pausePlayback'
+    })
+  }
+}
 </script>
diff --git a/front/src/components/audio/track/PlayIndicator.vue b/front/src/components/audio/track/PlayIndicator.vue
index c97b3c122..131a9f388 100644
--- a/front/src/components/audio/track/PlayIndicator.vue
+++ b/front/src/components/audio/track/PlayIndicator.vue
@@ -1,8 +1,8 @@
 <template>
   <div id="audio-bars">
-    <div class="audio-bar"></div>
-    <div class="audio-bar"></div>
-    <div class="audio-bar"></div>
-    <div class="audio-bar"></div>
+    <div class="audio-bar" />
+    <div class="audio-bar" />
+    <div class="audio-bar" />
+    <div class="audio-bar" />
   </div>
-</template>
\ No newline at end of file
+</template>
diff --git a/front/src/components/audio/track/Row.vue b/front/src/components/audio/track/Row.vue
index b2c7fd025..12c69f955 100644
--- a/front/src/components/audio/track/Row.vue
+++ b/front/src/components/audio/track/Row.vue
@@ -16,19 +16,18 @@
       <play-indicator
         v-if="
           !$store.state.player.isLoadingAudio &&
-          currentTrack &&
-          isPlaying &&
-          track.id === currentTrack.id &&
-          !(track.id == hover)
+            currentTrack &&
+            isPlaying &&
+            track.id === currentTrack.id &&
+            !(track.id == hover)
         "
-      >
-      </play-indicator>
+      />
       <button
         v-else-if="
           currentTrack &&
-          !isPlaying &&
-          track.id === currentTrack.id &&
-          !track.id == hover
+            !isPlaying &&
+            track.id === currentTrack.id &&
+            !track.id == hover
         "
         class="ui really tiny basic icon button play-button paused"
       >
@@ -37,9 +36,9 @@
       <button
         v-else-if="
           currentTrack &&
-          isPlaying &&
-          track.id === currentTrack.id &&
-          track.id == hover
+            isPlaying &&
+            track.id === currentTrack.id &&
+            track.id == hover
         "
         class="ui really tiny basic icon button play-button"
       >
@@ -51,7 +50,10 @@
       >
         <i class="play icon" />
       </button>
-      <span class="track-position" v-else-if="showPosition">
+      <span
+        v-else-if="showPosition"
+        class="track-position"
+      >
         {{ prettyPosition(track.position) }}
       </span>
     </div>
@@ -62,8 +64,6 @@
       @click.prevent.exact="activateTrack(track, index)"
     >
       <img
-        alt=""
-        class="ui artist-track mini image"
         v-if="
           track.album && track.album.cover && track.album.cover.urls.original
         "
@@ -72,10 +72,10 @@
             track.album.cover.urls.medium_square_crop
           )
         "
-      />
-      <img
         alt=""
         class="ui artist-track mini image"
+      >
+      <img
         v-else-if="
           track.cover && track.cover.urls.original
         "
@@ -84,10 +84,10 @@
             track.cover.urls.medium_square_crop
           )
         "
-      />
-      <img
         alt=""
         class="ui artist-track mini image"
+      >
+      <img
         v-else-if="
           track.artist && track.artist.cover && track.album.cover.urls.original
         "
@@ -96,36 +96,49 @@
             track.cover.urls.medium_square_crop
           )
         "
-      />
-      <img
         alt=""
         class="ui artist-track mini image"
+      >
+      <img
         v-else
+        alt=""
+        class="ui artist-track mini image"
         src="../../../assets/audio/default-cover.png"
-      />
+      >
     </div>
-    <div tabindex=0 class="content ellipsis left floated column">
+    <div
+      tabindex="0"
+      class="content ellipsis left floated column"
+    >
       <a
         @click="activateTrack(track, index)"
       >
         {{ track.title }}
       </a>
     </div>
-    <div v-if="showAlbum" class="content ellipsis left floated column">
+    <div
+      v-if="showAlbum"
+      class="content ellipsis left floated column"
+    >
       <router-link
         :to="{ name: 'library.albums.detail', params: { id: track.album.id } }"
-        >{{ track.album.title }}</router-link
       >
+        {{ track.album.title }}
+      </router-link>
     </div>
-    <div v-if="showArtist" class="content ellipsis left floated column">
+    <div
+      v-if="showArtist"
+      class="content ellipsis left floated column"
+    >
       <router-link
         class="artist link"
         :to="{
           name: 'library.artists.detail',
           params: { id: track.artist.id },
         }"
-        >{{ track.artist.name }}</router-link
       >
+        {{ track.artist.name }}
+      </router-link>
     </div>
     <div
       v-if="$store.state.auth.authenticated"
@@ -135,15 +148,21 @@
         class="tiny"
         :border="false"
         :track="track"
-      ></track-favorite-icon>
+      />
     </div>
-    <div v-if="showDuration" class="meta right floated column">
+    <div
+      v-if="showDuration"
+      class="meta right floated column"
+    >
       <human-duration
         v-if="track.uploads[0] && track.uploads[0].duration"
         :duration="track.uploads[0].duration"
-      ></human-duration>
+      />
     </div>
-    <div v-if="displayActions" class="meta right floated column">
+    <div
+      v-if="displayActions"
+      class="meta right floated column"
+    >
       <play-button
         id="playmenu"
         class="play-button basic icon"
@@ -155,22 +174,28 @@
           'large really discrete',
         ]"
         :track="track"
-      ></play-button>
+      />
     </div>
   </div>
 </template>
 
 <script>
-import PlayIndicator from "@/components/audio/track/PlayIndicator";
-import { mapActions, mapGetters } from "vuex";
-import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon";
-import PlayButton from "@/components/audio/PlayButton";
-import PlayOptions from "@/components/mixins/PlayOptions";
+import PlayIndicator from '@/components/audio/track/PlayIndicator'
+import { mapActions, mapGetters } from 'vuex'
+import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
+import PlayButton from '@/components/audio/PlayButton'
+import PlayOptions from '@/components/mixins/PlayOptions'
 
 export default {
+
+  components: {
+    PlayIndicator,
+    TrackFavoriteIcon,
+    PlayButton
+  },
   mixins: [PlayOptions],
   props: {
-    tracks: Array,
+    tracks: { type: Array, required: true },
     showAlbum: { type: Boolean, required: false, default: true },
     showArtist: { type: Boolean, required: false, default: true },
     showPosition: { type: Boolean, required: false, default: false },
@@ -181,45 +206,39 @@ export default {
     displayActions: { type: Boolean, required: false, default: true },
     showDuration: { type: Boolean, required: false, default: true },
     index: { type: Number, required: true },
-    track: { type: Object, required: true },
+    track: { type: Object, required: true }
   },
 
-  data() {
+  data () {
     return {
-      hover: null,
+      hover: null
     }
   },
 
-  components: {
-    PlayIndicator,
-    TrackFavoriteIcon,
-    PlayButton,
-  },
-
   computed: {
     ...mapGetters({
-      currentTrack: "queue/currentTrack",
+      currentTrack: 'queue/currentTrack'
     }),
 
-    isPlaying() {
-      return this.$store.state.player.playing;
-    },
+    isPlaying () {
+      return this.$store.state.player.playing
+    }
   },
 
   methods: {
 
-    prettyPosition(position, size) {
-      var s = String(position);
+    prettyPosition (position, size) {
+      let s = String(position)
       while (s.length < (size || 2)) {
-        s = "0" + s;
+        s = '0' + s
       }
-      return s;
+      return s
     },
-    
+
     ...mapActions({
-      resumePlayback: "player/resumePlayback",
-      pausePlayback: "player/pausePlayback",
-    }),
-  },
-};
+      resumePlayback: 'player/resumePlayback',
+      pausePlayback: 'player/pausePlayback'
+    })
+  }
+}
 </script>
diff --git a/front/src/components/audio/track/Table.vue b/front/src/components/audio/track/Table.vue
index ee21cfd43..1634de335 100644
--- a/front/src/components/audio/track/Table.vue
+++ b/front/src/components/audio/track/Table.vue
@@ -2,65 +2,95 @@
   <div>
     <!-- Show the search bar if search is true -->
     <inline-search-bar
-      v-model="query"
       v-if="search"
+      v-model="query"
       @search="
         additionalTracks = [];
         fetchData();
       "
-    ></inline-search-bar>
-    <div class="ui hidden divider"></div>
+    />
+    <div class="ui hidden divider" />
 
     <!-- Add a header if needed -->
 
-    <slot name="header"></slot>
+    <slot name="header" />
 
     <!-- Show a message if no tracks are available -->
 
-    <slot v-if="!isLoading && allTracks.length === 0" name="empty-state">
+    <slot
+      v-if="!isLoading && allTracks.length === 0"
+      name="empty-state"
+    >
       <empty-state
-        @refresh="fetchData('tracks/')"
         :refresh="true"
-      ></empty-state>
+        @refresh="fetchData('tracks/')"
+      />
     </slot>
     <div v-else>
       <div
         :class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-up']"
       >
-        <div v-if="isLoading" class="ui inverted active dimmer">
-          <div class="ui loader"></div>
+        <div
+          v-if="isLoading"
+          class="ui inverted active dimmer"
+        >
+          <div class="ui loader" />
         </div>
         <div class="track-table row">
-          <div v-if="showPosition" class="actions left floated column">
-            <i class="hashtag icon"></i>
+          <div
+            v-if="showPosition"
+            class="actions left floated column"
+          >
+            <i class="hashtag icon" />
           </div>
-          <div v-else class="actions left floated column"></div>
-          <div v-if="showArt" class="image left floated column"></div>
+          <div
+            v-else
+            class="actions left floated column"
+          />
+          <div
+            v-if="showArt"
+            class="image left floated column"
+          />
           <div class="content ellipsis left floated column">
             <b>{{ labels.title }}</b>
           </div>
-          <div v-if="showAlbum" class="content ellipsisleft floated column">
+          <div
+            v-if="showAlbum"
+            class="content ellipsisleft floated column"
+          >
             <b>{{ labels.album }}</b>
           </div>
-          <div v-if="showArtist" class="content ellipsis left floated column">
+          <div
+            v-if="showArtist"
+            class="content ellipsis left floated column"
+          >
             <b>{{ labels.artist }}</b>
           </div>
           <div
             v-if="$store.state.auth.authenticated"
             class="meta right floated column"
-          ></div>
-          <div v-if="showDuration" class="meta right floated column">
-            <i class="clock outline icon" style="padding: 0.5rem" />
+          />
+          <div
+            v-if="showDuration"
+            class="meta right floated column"
+          >
+            <i
+              class="clock outline icon"
+              style="padding: 0.5rem"
+            />
           </div>
-          <div v-if="displayActions" class="meta right floated column"></div>
+          <div
+            v-if="displayActions"
+            class="meta right floated column"
+          />
         </div>
 
         <!-- For each item, build a row -->
 
         <track-row
           v-for="(track, index) in allTracks"
-          :track="track"
           :key="track.id"
+          :track="track"
           :index="index"
           :tracks="allTracks"
           :show-album="showAlbum"
@@ -70,31 +100,37 @@
           :display-actions="displayActions"
           :show-duration="showDuration"
           :is-podcast="isPodcast"
-        ></track-row>
+        />
       </div>
-      <div v-if="paginateResults" class="ui center aligned basic segment desktop-and-up">
+      <div
+        v-if="paginateResults"
+        class="ui center aligned basic segment desktop-and-up"
+      >
         <pagination
           :total="total"
           :current="page"
           :paginate-by="paginateBy"
-          v-on="$listeners">
-        </pagination>
+          v-on="$listeners"
+        />
       </div>
     </div>
 
     <div
       :class="['track-table', 'ui', 'unstackable', 'grid', 'tablet-and-below']"
     >
-      <div v-if="isLoading" class="ui inverted active dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui inverted active dimmer"
+      >
+        <div class="ui loader" />
       </div>
 
       <!-- For each item, build a row -->
 
       <track-mobile-row
         v-for="(track, index) in allTracks"
-        :track="track"
         :key="track.id"
+        :track="track"
         :index="index"
         :tracks="allTracks"
         :show-position="showPosition"
@@ -103,35 +139,39 @@
         :is-artist="isArtist"
         :is-album="isAlbum"
         :is-podcast="isPodcast"
-      ></track-mobile-row>
-      <div v-if="paginateResults" class="ui center aligned basic segment tablet-and-below">
+      />
+      <div
+        v-if="paginateResults"
+        class="ui center aligned basic segment tablet-and-below"
+      >
         <pagination
           v-if="paginateResults"
           :total="total"
           :current="page"
           :compact="true"
-          v-on="$listeners"></pagination>
+          v-on="$listeners"
+        />
       </div>
     </div>
   </div>
 </template>
 
 <script>
-import _ from "@/lodash";
-import axios from "axios";
-import TrackRow from "@/components/audio/track/Row";
-import TrackMobileRow from "@/components/audio/track/MobileRow";
-import Pagination from "@/components/Pagination";
+import _ from '@/lodash'
+import axios from 'axios'
+import TrackRow from '@/components/audio/track/Row'
+import TrackMobileRow from '@/components/audio/track/MobileRow'
+import Pagination from '@/components/Pagination'
 
 export default {
   components: {
     TrackRow,
     TrackMobileRow,
-    Pagination,
+    Pagination
   },
 
   props: {
-    tracks: Array,
+    tracks: { type: Array, default: () => { return [] } },
     showAlbum: { type: Boolean, required: false, default: true },
     showArtist: { type: Boolean, required: false, default: true },
     showPosition: { type: Boolean, required: false, default: false },
@@ -144,66 +184,66 @@ export default {
     isArtist: { type: Boolean, required: false, default: false },
     isAlbum: { type: Boolean, required: false, default: false },
     isPodcast: { type: Boolean, required: false, default: false },
-    paginateResults: { type: Boolean, required: false, default: true},
-    total: { type: Number, required: false},
-    page: {type: Number, required: false, default: 1},
-    paginateBy: {type: Number, required: false, default: 25}
+    paginateResults: { type: Boolean, required: false, default: true },
+    total: { type: Number, required: false, default: 0 },
+    page: { type: Number, required: false, default: 1 },
+    paginateBy: { type: Number, required: false, default: 25 }
   },
 
-  data() {
+  data () {
     return {
       fetchDataUrl: this.nextUrl,
       isLoading: false,
       additionalTracks: [],
-      query: "",
-    };
+      query: ''
+    }
   },
 
   computed: {
-    allTracks() {
-      return (this.tracks || []).concat(this.additionalTracks);
+    allTracks () {
+      return (this.tracks || []).concat(this.additionalTracks)
     },
 
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext("*/*/*/Noun", "Title"),
-        album: this.$pgettext("*/*/*/Noun", "Album"),
-        artist: this.$pgettext("*/*/*/Noun", "Artist"),
-      };
-    },
+        title: this.$pgettext('*/*/*/Noun', 'Title'),
+        album: this.$pgettext('*/*/*/Noun', 'Album'),
+        artist: this.$pgettext('*/*/*/Noun', 'Artist')
+      }
+    }
+  },
+  created () {
+    if (!this.tracks) {
+      this.fetchData('tracks/')
+    }
   },
   methods: {
-    async fetchData(url) {
+    async fetchData (url) {
       if (!url) {
-        return;
+        return
       }
-      this.isLoading = true;
-      let self = this;
-      let params = _.clone(this.filters);
-      let tracksPromise = axios.get(url, { params: params })
-      params.page_size = this.limit;
-      params.page = this.page;
-      params.include_channels = true;
+      this.isLoading = true
+      const self = this
+      const params = _.clone(this.filters)
+      const tracksPromise = axios.get(url, { params: params })
+      params.page_size = this.limit
+      params.page = this.page
+      params.include_channels = true
       try {
         await tracksPromise
-        self.nextPage = tracksPromise.data.next;
-        self.objects = tracksPromise.data.results;
-        self.count = tracksPromise.data.count;
-        self.$emit("fetched", tracksPromise.data);
-        self.isLoading = false;
-      } catch(e) {
-          self.isLoading = false;
-          self.errors = error.backendErrors;
+        self.nextPage = tracksPromise.data.next
+        self.objects = tracksPromise.data.results
+        self.count = tracksPromise.data.count
+        self.$emit('fetched', tracksPromise.data)
+        self.isLoading = false
+      } catch (e) {
+        self.isLoading = false
+        self.errors = e.backendErrors
       }
     },
-    updatePage: function(page) {
+    updatePage: function (page) {
       this.$emit('page-changed', page)
     }
-  },
-  created() {
-    if (!this.tracks) {
-      this.fetchData("tracks/");
-    }
-  },
-};
+  }
+}
 </script>
diff --git a/front/src/components/audio/track/Widget.vue b/front/src/components/audio/track/Widget.vue
index 68d6988a9..85b7c6104 100644
--- a/front/src/components/audio/track/Widget.vue
+++ b/front/src/components/audio/track/Widget.vue
@@ -1,17 +1,48 @@
 <template>
   <div class="component-track-widget">
-    <h3 v-if="!!this.$slots.title">
-      <slot name="title"></slot>
-      <span v-if="showCount" class="ui tiny circular label">{{ count }}</span>
+    <h3 v-if="!!$slots.title">
+      <slot name="title" />
+      <span
+        v-if="showCount"
+        class="ui tiny circular label"
+      >{{ count }}</span>
     </h3>
-    <div v-if="count > 0" class="ui divided unstackable items">
-      <div :class="['item', itemClasses]" v-for="object in objects" :key="object.id">
+    <div
+      v-if="count > 0"
+      class="ui divided unstackable items"
+    >
+      <div
+        v-for="object in objects"
+        :key="object.id"
+        :class="['item', itemClasses]"
+      >
         <div class="ui tiny image">
-          <img alt="" v-if="object.track.album && object.track.album.cover" v-lazy="$store.getters['instance/absoluteUrl'](object.track.album.cover.urls.medium_square_crop)">
-          <img alt="" v-else-if="object.track.cover" v-lazy="$store.getters['instance/absoluteUrl'](object.track.cover.urls.medium_square_crop)"/>
-          <img alt="" v-else-if="object.track.artist.cover" v-lazy="$store.getters['instance/absoluteUrl'](object.track.artist.cover.urls.medium_square_crop)"/>
-          <img alt="" v-else src="../../../assets/audio/default-cover.png">
-          <play-button class="play-overlay" :icon-only="true" :button-classes="['ui', 'circular', 'tiny', 'vibrant', 'icon', 'button']" :track="object.track"></play-button>
+          <img
+            v-if="object.track.album && object.track.album.cover"
+            v-lazy="$store.getters['instance/absoluteUrl'](object.track.album.cover.urls.medium_square_crop)"
+            alt=""
+          >
+          <img
+            v-else-if="object.track.cover"
+            v-lazy="$store.getters['instance/absoluteUrl'](object.track.cover.urls.medium_square_crop)"
+            alt=""
+          >
+          <img
+            v-else-if="object.track.artist.cover"
+            v-lazy="$store.getters['instance/absoluteUrl'](object.track.artist.cover.urls.medium_square_crop)"
+            alt=""
+          >
+          <img
+            v-else
+            alt=""
+            src="../../../assets/audio/default-cover.png"
+          >
+          <play-button
+            class="play-overlay"
+            :icon-only="true"
+            :button-classes="['ui', 'circular', 'tiny', 'vibrant', 'icon', 'button']"
+            :track="object.track"
+          />
         </div>
         <div class="middle aligned content">
           <div class="ui unstackable grid">
@@ -23,15 +54,32 @@
               </div>
               <div class="meta ellipsis">
                 <span>
-                  <router-link class="discrete link" :to="{name: 'library.artists.detail', params: {id: object.track.artist.id}}">
+                  <router-link
+                    class="discrete link"
+                    :to="{name: 'library.artists.detail', params: {id: object.track.artist.id}}"
+                  >
                     {{ object.track.artist.name }}
                   </router-link>
                 </span>
               </div>
-              <tags-list label-classes="tiny" :truncate-size="20" :limit="2" :show-more="false" :tags="object.track.tags"></tags-list>
+              <tags-list
+                label-classes="tiny"
+                :truncate-size="20"
+                :limit="2"
+                :show-more="false"
+                :tags="object.track.tags"
+              />
 
-              <div class="extra" v-if="isActivity">
-                <router-link class="left floated" :to="{name: 'profile.overview', params: {username: object.user.username}}">@{{ object.user.username }}</router-link>
+              <div
+                v-if="isActivity"
+                class="extra"
+              >
+                <router-link
+                  class="left floated"
+                  :to="{name: 'profile.overview', params: {username: object.user.username}}"
+                >
+                  @{{ object.user.username }}
+                </router-link>
                 <span class="right floated"><human-date :date="object.creation_date" /></span>
               </div>
             </div>
@@ -41,30 +89,46 @@
                 :account="object.actor"
                 :dropdown-only="true"
                 :dropdown-icon-classes="['ellipsis', 'vertical', 'large really discrete']"
-                :track="object.track"></play-button>
+                :track="object.track"
+              />
             </div>
           </div>
         </div>
       </div>
-      <div v-if="isLoading" class="ui inverted active dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui inverted active dimmer"
+      >
+        <div class="ui loader" />
       </div>
     </div>
-    <div v-else class="ui placeholder segment">
+    <div
+      v-else
+      class="ui placeholder segment"
+    >
       <div class="ui icon header">
-        <i class="music icon"></i>
+        <i class="music icon" />
         <translate translate-context="Content/Home/Placeholder">
           Nothing found
         </translate>
       </div>
-      <div v-if="isLoading" class="ui inverted active dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui inverted active dimmer"
+      >
+        <div class="ui loader" />
       </div>
     </div>
     <template v-if="nextPage">
-      <div class="ui hidden divider"></div>
-      <button v-if="nextPage" @click="fetchData(nextPage)" :class="['ui', 'basic', 'button']">
-        <translate translate-context="*/*/Button,Label">Show more</translate>
+      <div class="ui hidden divider" />
+      <button
+        v-if="nextPage"
+        :class="['ui', 'basic', 'button']"
+        @click="fetchData(nextPage)"
+      >
+        <translate translate-context="*/*/Button,Label">
+          Show more
+        </translate>
       </button>
     </template>
   </div>
@@ -74,21 +138,21 @@
 import _ from '@/lodash'
 import axios from 'axios'
 import PlayButton from '@/components/audio/PlayButton'
-import TagsList from "@/components/tags/List"
+import TagsList from '@/components/tags/List'
 
 export default {
-  props: {
-    filters: {type: Object, required: true},
-    url: {type: String, required: true},
-    isActivity: {type: Boolean, default: true},
-    showCount: {type: Boolean, default: false},
-    limit: {type: Number, default: 5},
-    itemClasses: {type: String, default: ''},
-  },
   components: {
     PlayButton,
     TagsList
   },
+  props: {
+    filters: { type: Object, required: true },
+    url: { type: String, required: true },
+    isActivity: { type: Boolean, default: true },
+    showCount: { type: Boolean, default: false },
+    limit: { type: Number, default: 5 },
+    itemClasses: { type: String, default: '' }
+  },
   data () {
     return {
       objects: [],
@@ -99,6 +163,17 @@ export default {
       nextPage: null
     }
   },
+  watch: {
+    offset () {
+      this.fetchData()
+    },
+    '$store.state.moderation.lastUpdate': function () {
+      this.fetchData(this.url)
+    },
+    count (v) {
+      this.$emit('count', v)
+    }
+  },
   created () {
     this.fetchData(this.url)
   },
@@ -108,11 +183,11 @@ export default {
         return
       }
       this.isLoading = true
-      let self = this
-      let params = _.clone(this.filters)
+      const self = this
+      const params = _.clone(this.filters)
       params.page_size = this.limit
       params.offset = this.offset
-      axios.get(url, {params: params}).then((response) => {
+      axios.get(url, { params: params }).then((response) => {
         self.previousPage = response.data.previous
         self.nextPage = response.data.next
         self.isLoading = false
@@ -123,7 +198,7 @@ export default {
           newObjects = response.data.results
         } else {
           newObjects = response.data.results.map((r) => {
-            return {track: r}
+            return { track: r }
           })
         }
         self.objects = [...self.objects, ...newObjects]
@@ -139,17 +214,6 @@ export default {
         this.offset = Math.max(this.offset - this.limit, 0)
       }
     }
-  },
-  watch: {
-    offset () {
-      this.fetchData()
-    },
-    "$store.state.moderation.lastUpdate": function () {
-      this.fetchData(this.url)
-    },
-    count (v) {
-      this.$emit('count', v)
-    }
   }
 }
 </script>
diff --git a/front/src/components/auth/ApplicationEdit.vue b/front/src/components/auth/ApplicationEdit.vue
index b4a0b3735..4d04f9cdb 100644
--- a/front/src/components/auth/ApplicationEdit.vue
+++ b/front/src/components/auth/ApplicationEdit.vue
@@ -1,16 +1,26 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
     <div class="ui vertical stripe segment">
       <section class="ui text container">
-        <div v-if="isLoading" class="ui inverted active dimmer">
-          <div class="ui loader"></div>
+        <div
+          v-if="isLoading"
+          class="ui inverted active dimmer"
+        >
+          <div class="ui loader" />
         </div>
         <template v-else>
           <router-link :to="{name: 'settings'}">
-            <translate translate-context="Content/Applications/Link">Back to settings</translate>
+            <translate translate-context="Content/Applications/Link">
+              Back to settings
+            </translate>
           </router-link>
           <h2 class="ui header">
-            <translate translate-context="Content/Applications/Title">Application details</translate>
+            <translate translate-context="Content/Applications/Title">
+              Application details
+            </translate>
           </h2>
           <div class="ui form">
             <p>
@@ -20,25 +30,45 @@
             </p>
             <div class="field">
               <label for="copy-id"><translate translate-context="Content/Applications/Label">Application ID</translate></label>
-              <copy-input id="copy-id" :value="application.client_id" />
+              <copy-input
+                id="copy-id"
+                :value="application.client_id"
+              />
             </div>
             <div class="field">
               <label for="copy-secret"><translate translate-context="Content/Applications/Label">Application secret</translate></label>
-              <copy-input id="copy-secret" :value="application.client_secret" />
+              <copy-input
+                id="copy-secret"
+                :value="application.client_secret"
+              />
             </div>
-            <div class="field" v-if="application.token != undefined">
+            <div
+              v-if="application.token != undefined"
+              class="field"
+            >
               <label for="copy-secret"><translate translate-context="Content/Applications/Label">Access token</translate></label>
-              <copy-input id="copy-secret" :value="application.token" />
-              <a href="" @click.prevent="refreshToken">
-                <i class="refresh icon"></i>
+              <copy-input
+                id="copy-secret"
+                :value="application.token"
+              />
+              <a
+                href=""
+                @click.prevent="refreshToken"
+              >
+                <i class="refresh icon" />
                 <translate translate-context="Content/Applications/Label">Regenerate token</translate>
               </a>
             </div>
           </div>
           <h2 class="ui header">
-            <translate translate-context="Content/Applications/Title">Edit application</translate>
+            <translate translate-context="Content/Applications/Title">
+              Edit application
+            </translate>
           </h2>
-          <application-form @updated="application = $event" :app="application" />
+          <application-form
+            :app="application"
+            @updated="application = $event"
+          />
         </template>
       </section>
     </div>
@@ -46,19 +76,26 @@
 </template>
 
 <script>
-import axios from "axios"
+import axios from 'axios'
 
-import ApplicationForm from "@/components/auth/ApplicationForm"
+import ApplicationForm from '@/components/auth/ApplicationForm'
 
 export default {
-  props: ['id'],
   components: {
     ApplicationForm
   },
-  data() {
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       application: null,
-      isLoading: false,
+      isLoading: false
+    }
+  },
+  computed: {
+    labels () {
+      return {
+        title: this.$pgettext('Content/Applications/Title', 'Edit application')
+      }
     }
   },
   created () {
@@ -67,7 +104,7 @@ export default {
   methods: {
     fetchApplication () {
       this.isLoading = true
-      let self = this
+      const self = this
       axios.get(`oauth/apps/${this.id}/`).then((response) => {
         self.isLoading = false
         self.application = response.data
@@ -78,17 +115,10 @@ export default {
     },
     async refreshToken () {
       self.isLoading = true
-      let response = await axios.post(`oauth/apps/${this.id}/refresh-token`)
+      const response = await axios.post(`oauth/apps/${this.id}/refresh-token`)
       this.application = response.data
       self.isLoading = false
     }
-  },
-  computed: {
-    labels() {
-      return {
-        title: this.$pgettext('Content/Applications/Title', "Edit application")
-      }
-    },
   }
 }
 </script>
diff --git a/front/src/components/auth/ApplicationForm.vue b/front/src/components/auth/ApplicationForm.vue
index 4ddd5bc1a..bfbcd5808 100644
--- a/front/src/components/auth/ApplicationForm.vue
+++ b/front/src/components/auth/ApplicationForm.vue
@@ -1,19 +1,45 @@
 <template>
-
-  <form class="ui form component-form" role="alert" @submit.prevent="submit()">
-    <div v-if="errors.length > 0" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/*/Error message.Title">We cannot save your changes</translate></h4>
+  <form
+    class="ui form component-form"
+    role="alert"
+    @submit.prevent="submit()"
+  >
+    <div
+      v-if="errors.length > 0"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/*/Error message.Title">
+          We cannot save your changes
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
     <div class="ui field">
       <label for="application-name"><translate translate-context="*/*/*/Noun">Name</translate></label>
-      <input id="application-name" name="name" required type="text" v-model="fields.name" />
+      <input
+        id="application-name"
+        v-model="fields.name"
+        name="name"
+        required
+        type="text"
+      >
     </div>
     <div class="ui field">
       <label for="redirect-uris"><translate translate-context="Content/Applications/Input.Label/Noun">Redirect URI</translate></label>
-      <input id="redirect-uris" name="redirect_uris" type="text" v-model="fields.redirect_uris" />
+      <input
+        id="redirect-uris"
+        v-model="fields.redirect_uris"
+        name="redirect_uris"
+        type="text"
+      >
       <p class="help">
         <translate translate-context="Content/Applications/Help Text">
           Use "urn:ietf:wg:oauth:2.0:oob" as a redirect URI if your application is not served on the web.
@@ -28,13 +54,18 @@
         </translate>
       </p>
       <div class="ui stackable two column grid">
-        <div v-for="parent in allScopes" class="column">
+        <div
+          v-for="(parent, key) in allScopes"
+          :key="key"
+          class="column"
+        >
           <div class="ui parent checkbox">
             <input
+              :id="parent.id"
               v-model="scopeArray"
               :value="parent.id"
-              :id="parent.id"
-              type="checkbox">
+              type="checkbox"
+            >
             <label :for="parent.id">
               {{ parent.label }}
               <p class="help">
@@ -43,13 +74,17 @@
             </label>
           </div>
 
-          <div v-for="child in parent.children">
+          <div
+            v-for="(child, index) in parent.children"
+            :key="index"
+          >
             <div class="ui child checkbox">
               <input
+                :id="child.id"
                 v-model="scopeArray"
                 :value="child.id"
-                :id="child.id"
-                type="checkbox">
+                type="checkbox"
+              >
               <label :for="child.id">
                 {{ child.id }}
                 <p class="help">
@@ -60,29 +95,43 @@
           </div>
         </div>
       </div>
-
-      </div>
-    <button :class="['ui', {'loading': isLoading}, 'success', 'button']" type="submit">
-      <translate v-if="updating" key="2" translate-context="Content/Applications/Button.Label/Verb">Update application</translate>
-      <translate v-else key="3" translate-context="Content/Applications/Button.Label/Verb">Create application</translate>
+    </div>
+    <button
+      :class="['ui', {'loading': isLoading}, 'success', 'button']"
+      type="submit"
+    >
+      <translate
+        v-if="updating"
+        key="2"
+        translate-context="Content/Applications/Button.Label/Verb"
+      >
+        Update application
+      </translate>
+      <translate
+        v-else
+        key="3"
+        translate-context="Content/Applications/Button.Label/Verb"
+      >
+        Create application
+      </translate>
     </button>
   </form>
 </template>
 
 <script>
-import _ from "@/lodash"
-import axios from "axios"
-import TranslationsMixin from "@/components/mixins/Translations"
+import _ from '@/lodash'
+import axios from 'axios'
+import TranslationsMixin from '@/components/mixins/Translations'
 
 export default {
   mixins: [TranslationsMixin],
   props: {
-    app: {type: Object, required: false},
-    defaults: {type: Object, required: false}
+    app: { type: Object, required: false, default: () => { return {} } },
+    defaults: { type: Object, required: false, default: () => { return {} } }
   },
-  data() {
-    let app = this.app || {}
-    let defaults = this.defaults || {}
+  data () {
+    const app = this.app || {}
+    const defaults = this.defaults || {}
     return {
       isLoading: false,
       errors: [],
@@ -92,45 +141,19 @@ export default {
         scopes: app.scopes || defaults.scopes || 'read'
       },
       scopes: [
-        {id: "profile", icon: 'user'},
-        {id: "libraries", icon: 'book'},
-        {id: "favorites", icon: 'heart'},
-        {id: "listenings", icon: 'music'},
-        {id: "follows", icon: 'users'},
-        {id: "playlists", icon: 'list'},
-        {id: "radios", icon: 'rss'},
-        {id: "filters", icon: 'eye slash'},
-        {id: "notifications", icon: 'bell'},
-        {id: "edits", icon: 'pencil alternate'},
+        { id: 'profile', icon: 'user' },
+        { id: 'libraries', icon: 'book' },
+        { id: 'favorites', icon: 'heart' },
+        { id: 'listenings', icon: 'music' },
+        { id: 'follows', icon: 'users' },
+        { id: 'playlists', icon: 'list' },
+        { id: 'radios', icon: 'rss' },
+        { id: 'filters', icon: 'eye slash' },
+        { id: 'notifications', icon: 'bell' },
+        { id: 'edits', icon: 'pencil alternate' }
       ]
     }
   },
-  methods: {
-    submit () {
-      this.errors = []
-      let self = this
-      self.isLoading = true
-      let payload = this.fields
-      let event, promise, message
-      if (this.updating) {
-        event = 'updated'
-        promise = axios.patch(`oauth/apps/${this.app.client_id}/`, payload)
-      }  else {
-        event = 'created'
-        promise = axios.post(`oauth/apps/`, payload)
-      }
-      return promise.then(
-        response => {
-          self.isLoading = false
-          self.$emit(event, response.data)
-        },
-        error => {
-          self.isLoading = false
-          self.errors = error.backendErrors
-        }
-      )
-    },
-  },
   computed: {
     updating () {
       return this.app
@@ -144,8 +167,8 @@ export default {
       }
     },
     allScopes () {
-      let self = this
-      let parents = [
+      const self = this
+      const parents = [
         {
           id: 'read',
           label: this.$pgettext('Content/OAuth Scopes/Label/Verb', 'Read'),
@@ -157,19 +180,45 @@ export default {
           label: this.$pgettext('Content/OAuth Scopes/Label/Verb', 'Write'),
           description: this.$pgettext('Content/OAuth Scopes/Help Text', 'Write-only access to user data'),
           value: this.scopeArray.indexOf('write') > -1
-        },
+        }
       ]
       parents.forEach((p) => {
         p.children = self.scopes.map(s => {
-          let id = `${p.id}:${s.id}`
+          const id = `${p.id}:${s.id}`
           return {
             id,
-            value: this.scopeArray.indexOf(id) > -1,
+            value: this.scopeArray.indexOf(id) > -1
           }
         })
       })
       return parents
     }
+  },
+  methods: {
+    submit () {
+      this.errors = []
+      const self = this
+      self.isLoading = true
+      const payload = this.fields
+      let event, promise
+      if (this.updating) {
+        event = 'updated'
+        promise = axios.patch(`oauth/apps/${this.app.client_id}/`, payload)
+      } else {
+        event = 'created'
+        promise = axios.post('oauth/apps/', payload)
+      }
+      return promise.then(
+        response => {
+          self.isLoading = false
+          self.$emit(event, response.data)
+        },
+        error => {
+          self.isLoading = false
+          self.errors = error.backendErrors
+        }
+      )
+    }
   }
 }
 </script>
diff --git a/front/src/components/auth/ApplicationNew.vue b/front/src/components/auth/ApplicationNew.vue
index 4c7bb903b..8ffa13fe3 100644
--- a/front/src/components/auth/ApplicationNew.vue
+++ b/front/src/components/auth/ApplicationNew.vue
@@ -1,46 +1,58 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
     <div class="ui vertical stripe segment">
       <section class="ui text container">
         <router-link :to="{name: 'settings'}">
-          <translate translate-context="Content/Applications/Link">Back to settings</translate>
+          <translate translate-context="Content/Applications/Link">
+            Back to settings
+          </translate>
         </router-link>
         <h2 class="ui header">
-          <translate translate-context="Content/Settings/Button.Label">Create a new application</translate>
+          <translate translate-context="Content/Settings/Button.Label">
+            Create a new application
+          </translate>
         </h2>
         <application-form
           :defaults="defaults"
-          @created="$router.push({name: 'settings.applications.edit', params: {id: $event.client_id}})" />
+          @created="$router.push({name: 'settings.applications.edit', params: {id: $event.client_id}})"
+        />
       </section>
     </div>
   </main>
 </template>
 
 <script>
-import ApplicationForm from "@/components/auth/ApplicationForm"
+import ApplicationForm from '@/components/auth/ApplicationForm'
 
 export default {
-  props: ['name', 'redirect_uris', 'scopes'],
   components: {
     ApplicationForm
   },
-   data() {
+  props: {
+    name: { type: String, required: true },
+    redirectUris: { type: String, required: true },
+    scopes: { type: Array, required: true }
+  },
+  data () {
     return {
       application: null,
       isLoading: false,
       defaults: {
         name: this.name,
-        redirect_uris: this.redirect_uris,
-        scopes: this.scopes,
+        redirectUris: this.redirectUris,
+        scopes: this.scopes
       }
     }
   },
   computed: {
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext('Content/Settings/Button.Label', "Create a new application")
+        title: this.$pgettext('Content/Settings/Button.Label', 'Create a new application')
       }
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/auth/Authorize.vue b/front/src/components/auth/Authorize.vue
index 2b7c823c2..d86252009 100644
--- a/front/src/components/auth/Authorize.vue
+++ b/front/src/components/auth/Authorize.vue
@@ -1,34 +1,91 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
     <section class="ui vertical stripe segment">
       <div class="ui small text container">
-        <h2><i class="lock open icon"></i><translate translate-context="Content/Auth/Title/Verb">Authorize third-party app</translate></h2>
-        <div v-if="errors.length > 0" role="alert" class="ui negative message">
-          <h4 v-if="application" class="header"><translate translate-context="Popup/Moderation/Error message">Error while authorizing application</translate></h4>
-          <h4 v-else class="header"><translate translate-context="Popup/Moderation/Error message">Error while fetching application data</translate></h4>
+        <h2>
+          <i class="lock open icon" /><translate translate-context="Content/Auth/Title/Verb">
+            Authorize third-party app
+          </translate>
+        </h2>
+        <div
+          v-if="errors.length > 0"
+          role="alert"
+          class="ui negative message"
+        >
+          <h4
+            v-if="application"
+            class="header"
+          >
+            <translate translate-context="Popup/Moderation/Error message">
+              Error while authorizing application
+            </translate>
+          </h4>
+          <h4
+            v-else
+            class="header"
+          >
+            <translate translate-context="Popup/Moderation/Error message">
+              Error while fetching application data
+            </translate>
+          </h4>
           <ul class="list">
-            <li v-for="error in errors">{{ error }}</li>
+            <li
+              v-for="(error, key) in errors"
+              :key="key"
+            >
+              {{ error }}
+            </li>
           </ul>
         </div>
-        <div v-if="isLoading" class="ui inverted active dimmer">
-          <div class="ui loader"></div>
+        <div
+          v-if="isLoading"
+          class="ui inverted active dimmer"
+        >
+          <div class="ui loader" />
         </div>
-        <form v-else-if="application && !code" :class="['ui', {loading: isLoading}, 'form']" @submit.prevent="submit">
-          <h3><translate translate-context="Content/Auth/Title" :translate-params="{app: application.name}">%{ app } wants to access your Funkwhale account</translate></h3>
+        <form
+          v-else-if="application && !code"
+          :class="['ui', {loading: isLoading}, 'form']"
+          @submit.prevent="submit"
+        >
+          <h3>
+            <translate
+              translate-context="Content/Auth/Title"
+              :translate-params="{app: application.name}"
+            >
+              %{ app } wants to access your Funkwhale account
+            </translate>
+          </h3>
 
-          <h4 v-for="topic in topicScopes" class="ui header vertical-align">
-            <span v-if="topic.write && !topic.read" :class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']">
-              <i class="pencil icon"></i>
+          <h4
+            v-for="(topic, key) in topicScopes"
+            :key="key"
+            class="ui header vertical-align"
+          >
+            <span
+              v-if="topic.write && !topic.read"
+              :class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
+            >
+              <i class="pencil icon" />
               <translate translate-context="Content/Auth/Label/Noun">Write-only</translate>
             </span>
-            <span v-else-if="!topic.write && topic.read" :class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']">
+            <span
+              v-else-if="!topic.write && topic.read"
+              :class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
+            >
               <translate translate-context="Content/Auth/Label/Noun">Read-only</translate>
             </span>
-            <span v-else-if="topic.write && topic.read" :class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']">
-              <i class="pencil icon"></i>
+            <span
+              v-else-if="topic.write && topic.read"
+              :class="['ui', 'basic', 'right floated', 'tiny', 'vertically-spaced component-label label']"
+            >
+              <i class="pencil icon" />
               <translate translate-context="Content/Auth/Label/Noun">Full access</translate>
             </span>
-            <i :class="[topic.icon, 'icon']"></i>
+            <i :class="[topic.icon, 'icon']" />
             <div class="content">
               {{ topic.label }}
               <div class="sub header">
@@ -38,23 +95,46 @@
           </h4>
           <div v-if="unknownRequestedScopes.length > 0">
             <p><strong><translate translate-context="Content/Auth/Paragraph">The application is also requesting the following unknown permissions:</translate></strong></p>
-            <ul v-for="scope in unknownRequestedScopes">
-              <li>{{ scope }}</li>
+            <ul
+              v-for="(unknownscope, key) in unknownRequestedScopes"
+              :key="key"
+            >
+              <li>{{ unknownscope }}</li>
             </ul>
-
           </div>
-          <button class="ui success labeled icon button" type="submit">
-            <i class="lock open icon"></i>
-            <translate translate-context="Content/Signup/Button.Label/Verb" :translate-params="{app: application.name}">Authorize %{ app }</translate>
+          <button
+            class="ui success labeled icon button"
+            type="submit"
+          >
+            <i class="lock open icon" />
+            <translate
+              translate-context="Content/Signup/Button.Label/Verb"
+              :translate-params="{app: application.name}"
+            >
+              Authorize %{ app }
+            </translate>
           </button>
-          <p v-if="redirectUri === 'urn:ietf:wg:oauth:2.0:oob'" key="1" v-translate translate-context="Content/Auth/Paragraph">
-            You will be shown a code to copy-paste in the application.</p>
-          <p v-else key="2" v-translate="{url: redirectUri}" translate-context="Content/Auth/Paragraph" :translate-params="{url: redirectUri}">You will be redirected to <strong>%{ url }</strong></p>
-
+          <p
+            v-if="redirectUri === 'urn:ietf:wg:oauth:2.0:oob'"
+            key="1"
+            v-translate
+            translate-context="Content/Auth/Paragraph"
+          >
+            You will be shown a code to copy-paste in the application.
+          </p>
+          <p
+            v-else
+            key="2"
+            v-translate="{url: redirectUri}"
+            translate-context="Content/Auth/Paragraph"
+            :translate-params="{url: redirectUri}"
+          >
+            You will be redirected to <strong>%{ url }</strong>
+          </p>
         </form>
         <div v-else-if="code">
           <p><strong><translate translate-context="Content/Auth/Paragraph">Copy-paste the following code in the application:</translate></strong></p>
-          <copy-input :value="code"></copy-input>
+          <copy-input :value="code" />
         </div>
       </div>
     </section>
@@ -62,60 +142,54 @@
 </template>
 
 <script>
-import TranslationsMixin from "@/components/mixins/Translations"
+import TranslationsMixin from '@/components/mixins/Translations'
 
 import axios from 'axios'
 
-import {checkRedirectToLogin} from '@/utils'
+import { checkRedirectToLogin } from '@/utils'
 export default {
   mixins: [TranslationsMixin],
-  props: [
-    'clientId',
-    'redirectUri',
-    'scope',
-    'responseType',
-    'nonce',
-    'state',
-  ],
-  data() {
+  props: {
+    clientId: { type: String, required: true },
+    redirectUri: { type: String, required: true },
+    scope: { type: String, required: true },
+    responseType: { type: String, required: true },
+    nonce: { type: String, required: true },
+    state: { type: String, required: true }
+  },
+  data () {
     return {
       application: null,
       isLoading: false,
       errors: [],
       code: null,
       knownScopes: [
-        {id: "profile", icon: 'user'},
-        {id: "libraries", icon: 'book'},
-        {id: "favorites", icon: 'heart'},
-        {id: "listenings", icon: 'music'},
-        {id: "follows", icon: 'users'},
-        {id: "playlists", icon: 'list'},
-        {id: "radios", icon: 'rss'},
-        {id: "filters", icon: 'eye slash'},
-        {id: "notifications", icon: 'bell'},
-        {id: "edits", icon: 'pencil alternate'},
-        {id: "security", icon: 'lock'},
-        {id: "reports", icon: 'warning sign'},
+        { id: 'profile', icon: 'user' },
+        { id: 'libraries', icon: 'book' },
+        { id: 'favorites', icon: 'heart' },
+        { id: 'listenings', icon: 'music' },
+        { id: 'follows', icon: 'users' },
+        { id: 'playlists', icon: 'list' },
+        { id: 'radios', icon: 'rss' },
+        { id: 'filters', icon: 'eye slash' },
+        { id: 'notifications', icon: 'bell' },
+        { id: 'edits', icon: 'pencil alternate' },
+        { id: 'security', icon: 'lock' },
+        { id: 'reports', icon: 'warning sign' }
       ]
     }
   },
-  created () {
-    checkRedirectToLogin(this.$store, this.$router)
-    if (this.clientId) {
-      this.fetchApplication()
-    }
-  },
   computed: {
     labels () {
       return {
-        title: this.$pgettext('Head/Authorize/Title', "Allow application")
+        title: this.$pgettext('Head/Authorize/Title', 'Allow application')
       }
     },
     requestedScopes () {
       return (this.scope || '').split(' ')
     },
     supportedScopes () {
-      let supported = ['read', 'write']
+      const supported = ['read', 'write']
       this.knownScopes.forEach(s => {
         supported.push(`read:${s.id}`)
         supported.push(`write:${s.id}`)
@@ -123,14 +197,14 @@ export default {
       return supported
     },
     unknownRequestedScopes () {
-      let self = this
+      const self = this
       return this.requestedScopes.filter(s => {
         return self.supportedScopes.indexOf(s) < 0
       })
     },
     topicScopes () {
-      let self = this
-      let requested = this.requestedScopes
+      const self = this
+      const requested = this.requestedScopes
       let write = false
       let read = false
       if (requested.indexOf('read') > -1) {
@@ -141,24 +215,30 @@ export default {
       }
 
       return this.knownScopes.map(s => {
-        let id = s.id
+        const id = s.id
         return {
           id: id,
           icon: s.icon,
           label: self.sharedLabels.scopes[s.id].label,
           description: self.sharedLabels.scopes[s.id].description,
           read: read || requested.indexOf(`read:${id}`) > -1,
-          write: write || requested.indexOf(`write:${id}`) > -1,
+          write: write || requested.indexOf(`write:${id}`) > -1
         }
       }).filter(c => {
         return c.read || c.write
       })
     }
   },
+  created () {
+    checkRedirectToLogin(this.$store, this.$router)
+    if (this.clientId) {
+      this.fetchApplication()
+    }
+  },
   methods: {
     fetchApplication () {
       this.isLoading = true
-      let self = this
+      const self = this
       axios.get(`oauth/apps/${this.clientId}/`).then((response) => {
         self.isLoading = false
         self.application = response.data
@@ -169,8 +249,8 @@ export default {
     },
     submit () {
       this.isLoading = true
-      let self = this
-      let data = new FormData();
+      const self = this
+      const data = new FormData()
       data.set('redirect_uri', this.redirectUri)
       data.set('scope', this.scope)
       data.set('allow', true)
@@ -178,7 +258,7 @@ export default {
       data.set('response_type', this.responseType)
       data.set('state', this.state)
       data.set('nonce', this.nonce)
-      axios.post(`oauth/authorize/`, data, {headers: {'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest'}}).then((response) => {
+      axios.post('oauth/authorize/', data, { headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' } }).then((response) => {
         if (self.redirectUri === 'urn:ietf:wg:oauth:2.0:oob') {
           self.isLoading = false
           self.code = response.data.code
diff --git a/front/src/components/auth/LoginForm.vue b/front/src/components/auth/LoginForm.vue
index b7515c427..cecade905 100644
--- a/front/src/components/auth/LoginForm.vue
+++ b/front/src/components/auth/LoginForm.vue
@@ -1,18 +1,35 @@
 <template>
-  <form class="ui form" @submit.prevent="submit()">
-    <div v-if="error" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/Login/Error message.Title">We cannot log you in</translate></h4>
+  <form
+    class="ui form"
+    @submit.prevent="submit()"
+  >
+    <div
+      v-if="error"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/Login/Error message.Title">
+          We cannot log you in
+        </translate>
+      </h4>
       <ul class="list">
         <li v-if="error == 'invalid_credentials' && $store.state.instance.settings.moderation.signup_approval_enabled.value">
-          <translate translate-context="Content/Login/Error message.List item/Call to action">If you signed-up recently, you may need to wait before our moderation team review your account, or verify your e-mail address.</translate>
+          <translate translate-context="Content/Login/Error message.List item/Call to action">
+            If you signed-up recently, you may need to wait before our moderation team review your account, or verify your e-mail address.
+          </translate>
         </li>
         <li v-else-if="error == 'invalid_credentials'">
-          <translate translate-context="Content/Login/Error message.List item/Call to action">Please double-check that your username and password combination is correct and make sure you verified your e-mail address.</translate>
+          <translate translate-context="Content/Login/Error message.List item/Call to action">
+            Please double-check that your username and password combination is correct and make sure you verified your e-mail address.
+          </translate>
+        </li>
+        <li v-else>
+          {{ error }}
         </li>
-        <li v-else>{{ error }}</li>
       </ul>
     </div>
-    <template v-if="$store.getters['instance/appDomain'] === $store.getters['instance/domain']" >
+    <template v-if="$store.getters['instance/appDomain'] === $store.getters['instance/domain']">
       <div class="field">
         <label for="username-field">
           <translate translate-context="Content/Login/Input.Label/Noun">Username or e-mail address</translate>
@@ -24,14 +41,14 @@
           </template>
         </label>
         <input
-        ref="username"
-        required
-        name="username"
-        type="text"
-        id="username-field"
-        autofocus
-        :placeholder="labels.usernamePlaceholder"
-        v-model="credentials.username"
+          id="username-field"
+          ref="username"
+          v-model="credentials.username"
+          required
+          name="username"
+          type="text"
+          autofocus
+          :placeholder="labels.usernamePlaceholder"
         >
       </div>
       <div class="field">
@@ -41,65 +58,78 @@
             <translate translate-context="*/Login/*/Verb">Reset your password</translate>
           </router-link>
         </label>
-        <password-input field-id="password-field" required v-model="credentials.password" />
-
+        <password-input
+          v-model="credentials.password"
+          field-id="password-field"
+          required
+        />
       </div>
     </template>
     <template v-else>
       <p>
-        <translate translate-context="Contant/Auth/Paragraph" :translate-params="{domain: $store.getters['instance/domain']}">You will be redirected to %{ domain } to authenticate.</translate>
+        <translate
+          translate-context="Contant/Auth/Paragraph"
+          :translate-params="{domain: $store.getters['instance/domain']}"
+        >
+          You will be redirected to %{ domain } to authenticate.
+        </translate>
       </p>
     </template>
-    <button :class="['ui', {'loading': isLoading}, 'right', 'floated', buttonClasses, 'button']" type="submit">
-      <translate translate-context="*/Login/*/Verb">Login</translate>
+    <button
+      :class="['ui', {'loading': isLoading}, 'right', 'floated', buttonClasses, 'button']"
+      type="submit"
+    >
+      <translate translate-context="*/Login/*/Verb">
+        Login
+      </translate>
     </button>
   </form>
 </template>
 
 <script>
-import PasswordInput from "@/components/forms/PasswordInput"
+import PasswordInput from '@/components/forms/PasswordInput'
 
 export default {
-  props: {
-    next: { type: String, default: "/library" },
-    buttonClasses: { type: String, default: "success" },
-    showSignup: { type: Boolean, default: true},
-  },
   components: {
     PasswordInput
   },
-  data() {
+  props: {
+    next: { type: String, default: '/library' },
+    buttonClasses: { type: String, default: 'success' },
+    showSignup: { type: Boolean, default: true }
+  },
+  data () {
     return {
       // We need to initialize the component with any
       // properties that will be used in it
       credentials: {
-        username: "",
-        password: ""
+        username: '',
+        password: ''
       },
-      error: "",
+      error: '',
       isLoading: false
     }
   },
+  computed: {
+    labels () {
+      const usernamePlaceholder = this.$pgettext('Content/Login/Input.Placeholder', 'Enter your username or e-mail address')
+      return {
+        usernamePlaceholder
+      }
+    }
+  },
   created () {
     if (this.$store.state.auth.authenticated) {
       this.$router.push(this.next)
     }
   },
-  mounted() {
+  mounted () {
     if (this.$refs.username) {
       this.$refs.username.focus()
     }
   },
-  computed: {
-    labels() {
-      let usernamePlaceholder = this.$pgettext('Content/Login/Input.Placeholder', "Enter your username or e-mail address")
-      return {
-        usernamePlaceholder,
-      }
-    }
-  },
   methods: {
-    async submit() {
+    async submit () {
       if (this.$store.getters['instance/appDomain'] === this.$store.getters['instance/domain']) {
         return await this.submitSession()
       } else {
@@ -107,21 +137,21 @@ export default {
         await this.$store.dispatch('auth/oauthLogin', this.next)
       }
     },
-    async submitSession() {
-      var self = this
+    async submitSession () {
+      const self = this
       self.isLoading = true
-      this.error = ""
-      var credentials = {
+      this.error = ''
+      const credentials = {
         username: this.credentials.username,
         password: this.credentials.password
       }
       this.$store
-        .dispatch("auth/login", {
+        .dispatch('auth/login', {
           credentials,
           next: this.next,
           onError: error => {
             if (error.response.status === 400) {
-              self.error = "invalid_credentials"
+              self.error = 'invalid_credentials'
             } else {
               self.error = error.backendErrors[0]
             }
diff --git a/front/src/components/auth/Logout.vue b/front/src/components/auth/Logout.vue
index cee056b7f..aa1a98041 100644
--- a/front/src/components/auth/Logout.vue
+++ b/front/src/components/auth/Logout.vue
@@ -1,18 +1,50 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
     <section class="ui vertical stripe segment">
-      <div v-if="$store.state.auth.authenticated" class="ui small text container">
+      <div
+        v-if="$store.state.auth.authenticated"
+        class="ui small text container"
+      >
         <h2>
-          <translate translate-context="Content/Login/Title">Are you sure you want to log out?</translate>
+          <translate translate-context="Content/Login/Title">
+            Are you sure you want to log out?
+          </translate>
         </h2>
-        <p v-translate="{username: $store.state.auth.username}" translate-context="Content/Login/Paragraph">You are currently logged in as %{ username }</p>
-        <button class="ui button" @click="$store.dispatch('auth/logout')"><translate translate-context="Content/Login/Button.Label">Yes, log me out!</translate></button>
+        <p
+          v-translate="{username: $store.state.auth.username}"
+          translate-context="Content/Login/Paragraph"
+        >
+          You are currently logged in as %{ username }
+        </p>
+        <button
+          class="ui button"
+          @click="$store.dispatch('auth/logout')"
+        >
+          <translate translate-context="Content/Login/Button.Label">
+            Yes, log me out!
+          </translate>
+        </button>
       </div>
-      <div v-else class="ui small text container">
+      <div
+        v-else
+        class="ui small text container"
+      >
         <h2>
-          <translate translate-context="Content/Login/Title">You aren't currently logged in</translate>
+          <translate translate-context="Content/Login/Title">
+            You aren't currently logged in
+          </translate>
         </h2>
-        <router-link to='/login' class="ui button"><translate translate-context="Content/Login/Button.Label">Log in!</translate></router-link>
+        <router-link
+          to="/login"
+          class="ui button"
+        >
+          <translate translate-context="Content/Login/Button.Label">
+            Log in!
+          </translate>
+        </router-link>
       </div>
     </section>
   </main>
@@ -21,9 +53,9 @@
 <script>
 export default {
   computed: {
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext('Head/Login/Title', "Log Out")
+        title: this.$pgettext('Head/Login/Title', 'Log Out')
       }
     }
   }
diff --git a/front/src/components/auth/Plugin.vue b/front/src/components/auth/Plugin.vue
index 571cd7f55..5967f013b 100644
--- a/front/src/components/auth/Plugin.vue
+++ b/front/src/components/auth/Plugin.vue
@@ -1,96 +1,193 @@
 <template>
-  <form :class="['ui segment form', {loading: isLoading}]" @submit.prevent="submit">  
+  <form
+    :class="['ui segment form', {loading: isLoading}]"
+    @submit.prevent="submit"
+  >
     <h3>{{ plugin.label }}</h3>
-    <div v-if="plugin.description" v-html="markdown.makeHtml(plugin.description)"></div>
-    <template v-if="plugin.homepage" >
-      <div class="ui small hidden divider"></div>
-      <a :href="plugin.homepage" target="_blank">
-        <i class="external icon"></i>
+    <div
+      v-if="plugin.description"
+      v-html="markdown.makeHtml(plugin.description)"
+    />
+    <template v-if="plugin.homepage">
+      <div class="ui small hidden divider" />
+      <a
+        :href="plugin.homepage"
+        target="_blank"
+      >
+        <i class="external icon" />
         <translate translate-context="Footer/*/List item.Link/Short, Noun">Documentation</translate>
       </a>
     </template>
-    <div class="ui clearing hidden divider"></div> 
-    <div v-if="errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/*/Error message.Title">Error while saving plugin</translate></h4>
+    <div class="ui clearing hidden divider" />
+    <div
+      v-if="errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/*/Error message.Title">
+          Error while saving plugin
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
     <div class="field">
       <div class="ui toggle checkbox">
-        <input :id="`${plugin.name}-enabled`" type="checkbox" v-model="enabled" />
+        <input
+          :id="`${plugin.name}-enabled`"
+          v-model="enabled"
+          type="checkbox"
+        >
         <label :for="`${plugin.name}-enabled`"><translate translate-context="*/*/*">Enabled</translate></label>
       </div>
     </div>
-    <div class="ui clearing hidden divider"></div> 
-    <div v-if="plugin.source" class="field">
+    <div class="ui clearing hidden divider" />
+    <div
+      v-if="plugin.source"
+      class="field"
+    >
       <label for="plugin-library"><translate translate-context="*/*/*/Noun">Library</translate></label>
-      <select id="plugin-library" v-model="values['library']">
-        <option :value="l.uuid" v-for="l in libraries" :key="l.uuid">{{ l.name }}</option>
+      <select
+        id="plugin-library"
+        v-model="values['library']"
+      >
+        <option
+          v-for="l in libraries"
+          :key="l.uuid"
+          :value="l.uuid"
+        >
+          {{ l.name }}
+        </option>
       </select>
       <div>
-        <translate translate-context="*/*/Paragraph/Noun">Library where files should be imported.</translate>
+        <translate translate-context="*/*/Paragraph/Noun">
+          Library where files should be imported.
+        </translate>
       </div>
     </div>
-    <template v-if="plugin.conf && plugin.conf.length > 0" v-for="field in plugin.conf">
-      <div v-if="field.type === 'text'" class="field">
+    <template
+      v-for="(field, key) in plugin.conf"
+      v-if="plugin.conf && plugin.conf.length > 0"
+    >
+      <div
+        v-if="field.type === 'text'"
+        :key="key"
+        class="field"
+      >
         <label :for="`plugin-${field.name}`">{{ field.label || field.name }}</label>
-        <input :id="`plugin-${field.name}`" type="text" v-model="values[field.name]">
-        <div v-if="field.help" v-html="markdown.makeHtml(field.help)"></div>
+        <input
+          :id="`plugin-${field.name}`"
+          v-model="values[field.name]"
+          type="text"
+        >
+        <div
+          v-if="field.help"
+          v-html="markdown.makeHtml(field.help)"
+        />
       </div>
-      <div v-if="field.type === 'long_text'" class="field">
+      <div
+        v-if="field.type === 'long_text'"
+        :key="key"
+        class="field"
+      >
         <label :for="`plugin-${field.name}`">{{ field.label || field.name }}</label>
-        <textarea :id="`plugin-${field.name}`" type="text" v-model="values[field.name]" rows="5" />
-        <div v-if="field.help" v-html="markdown.makeHtml(field.help)"></div>
+        <textarea
+          :id="`plugin-${field.name}`"
+          v-model="values[field.name]"
+          type="text"
+          rows="5"
+        />
+        <div
+          v-if="field.help"
+          v-html="markdown.makeHtml(field.help)"
+        />
       </div>
-      <div v-if="field.type === 'url'" class="field">
+      <div
+        v-if="field.type === 'url'"
+        :key="key"
+        class="field"
+      >
         <label :for="`plugin-${field.name}`">{{ field.label || field.name }}</label>
-        <input :id="`plugin-${field.name}`" type="url" v-model="values[field.name]">
-        <div v-if="field.help" v-html="markdown.makeHtml(field.help)"></div>
+        <input
+          :id="`plugin-${field.name}`"
+          v-model="values[field.name]"
+          type="url"
+        >
+        <div
+          v-if="field.help"
+          v-html="markdown.makeHtml(field.help)"
+        />
       </div>
-      <div v-if="field.type === 'password'" class="field">
+      <div
+        v-if="field.type === 'password'"
+        :key="key"
+        class="field"
+      >
         <label :for="`plugin-${field.name}`">{{ field.label || field.name }}</label>
-        <input :id="`plugin-${field.name}`" type="password" v-model="values[field.name]">
-        <div v-if="field.help" v-html="markdown.makeHtml(field.help)"></div>
+        <input
+          :id="`plugin-${field.name}`"
+          v-model="values[field.name]"
+          type="password"
+        >
+        <div
+          v-if="field.help"
+          v-html="markdown.makeHtml(field.help)"
+        />
       </div>
     </template>
     <button
       type="submit"
-      :class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']">
-        <translate translate-context="Content/*/Button.Label/Verb">Save</translate>
+      :class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
+    >
+      <translate translate-context="Content/*/Button.Label/Verb">
+        Save
+      </translate>
     </button>
     <button
-      type="scan"
       v-if="plugin.source"
+      type="scan"
+      :class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
       @click.prevent="submitAndScan"
-      :class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']">
-        <translate translate-context="Content/*/Button.Label/Verb">Scan</translate>
+    >
+      <translate translate-context="Content/*/Button.Label/Verb">
+        Scan
+      </translate>
     </button>
-    <div class="ui clearing hidden divider"></div> 
+    <div class="ui clearing hidden divider" />
   </form>
 </template>
 
 <script>
-import axios from "axios"
+import axios from 'axios'
 import lodash from '@/lodash'
 import showdown from 'showdown'
 export default {
-  props: ['plugin', "libraries"],
+  props: {
+    plugin: { type: Object, required: true },
+    libraries: { type: Array, required: true }
+  },
   data () {
     return {
       markdown: new showdown.Converter(),
       isLoading: false,
       enabled: this.plugin.enabled,
       values: lodash.clone(this.plugin.values || {}),
-      errors: [],
+      errors: []
     }
   },
   methods: {
     async submit () {
       this.isLoading = true
       this.errors = []
-      let url = `plugins/${this.plugin.name}`
-      let enableUrl = this.enabled ? `${url}/enable` : `${url}/disable`
+      const url = `plugins/${this.plugin.name}`
+      const enableUrl = this.enabled ? `${url}/enable` : `${url}/disable`
       await axios.post(enableUrl)
       try {
         await axios.post(url, this.values)
@@ -101,19 +198,19 @@ export default {
     },
     async scan () {
       this.isLoading = true
-        this.errors = []
-        let url = `plugins/${this.plugin.name}/scan`
-        try {
-          await axios.post(url, this.values)
-        } catch (e) {
-          this.errors = e.backendErrors
-        }
-        this.isLoading = false
+      this.errors = []
+      const url = `plugins/${this.plugin.name}/scan`
+      try {
+        await axios.post(url, this.values)
+      } catch (e) {
+        this.errors = e.backendErrors
+      }
+      this.isLoading = false
     },
     async submitAndScan () {
       await this.submit()
       await this.scan()
     }
-  },
+  }
 }
 </script>
diff --git a/front/src/components/auth/SignupForm.vue b/front/src/components/auth/SignupForm.vue
index 35bb8df7b..0711178ae 100644
--- a/front/src/components/auth/SignupForm.vue
+++ b/front/src/components/auth/SignupForm.vue
@@ -2,140 +2,197 @@
   <div v-if="submitted">
     <div class="ui success message">
       <p v-if="signupRequiresApproval">
-        <translate translate-context="Content/Signup/Form/Paragraph">Your account request was successfully submitted. You will be notified by e-mail when our moderation team has reviewed your request.</translate>
+        <translate translate-context="Content/Signup/Form/Paragraph">
+          Your account request was successfully submitted. You will be notified by e-mail when our moderation team has reviewed your request.
+        </translate>
       </p>
       <p v-else>
-        <translate translate-context="Content/Signup/Form/Paragraph">Your account was successfully created. Please verify your e-mail address before trying to login.</translate>
+        <translate translate-context="Content/Signup/Form/Paragraph">
+          Your account was successfully created. Please verify your e-mail address before trying to login.
+        </translate>
       </p>
     </div>
-    <h2><translate translate-context="Content/Login/Title/Verb">Log in to your Funkwhale account</translate></h2>
-    <login-form button-classes="basic success" :show-signup="false"></login-form>
+    <h2>
+      <translate translate-context="Content/Login/Title/Verb">
+        Log in to your Funkwhale account
+      </translate>
+    </h2>
+    <login-form
+      button-classes="basic success"
+      :show-signup="false"
+    />
   </div>
   <form
     v-else
     :class="['ui', {'loading': isLoadingInstanceSetting}, 'form']"
-    @submit.prevent="submit()">
-    <p class="ui message" v-if="!$store.state.instance.settings.users.registration_enabled.value">
-      <translate translate-context="Content/Signup/Form/Paragraph">Public registrations are not possible on this instance. You will need an invitation code to sign up.</translate>
+    @submit.prevent="submit()"
+  >
+    <p
+      v-if="!$store.state.instance.settings.users.registration_enabled.value"
+      class="ui message"
+    >
+      <translate translate-context="Content/Signup/Form/Paragraph">
+        Public registrations are not possible on this instance. You will need an invitation code to sign up.
+      </translate>
     </p>
-    <p class="ui message" v-else-if="signupRequiresApproval">
-      <translate translate-context="Content/Signup/Form/Paragraph">Registrations on this pod are open, but reviewed by moderators before approval.</translate>
+    <p
+      v-else-if="signupRequiresApproval"
+      class="ui message"
+    >
+      <translate translate-context="Content/Signup/Form/Paragraph">
+        Registrations on this pod are open, but reviewed by moderators before approval.
+      </translate>
     </p>
     <template v-if="formCustomization && formCustomization.help_text">
-      <rendered-description :content="formCustomization.help_text" :fetch-html="fetchDescriptionHtml" :permissive="true"></rendered-description>
-      <div class="ui hidden divider"></div>
+      <rendered-description
+        :content="formCustomization.help_text"
+        :fetch-html="fetchDescriptionHtml"
+        :permissive="true"
+      />
+      <div class="ui hidden divider" />
     </template>
-    <div v-if="errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/Signup/Form/Paragraph">Your account cannot be created.</translate></h4>
+    <div
+      v-if="errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/Signup/Form/Paragraph">
+          Your account cannot be created.
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
     <div class="required field">
       <label for="username-field"><translate translate-context="Content/*/*">Username</translate></label>
       <input
-      ref="username"
-      name="username"
-      required
-      id="username-field"
-      type="text"
-      autofocus
-      :placeholder="labels.usernamePlaceholder"
-      v-model="username">
+        id="username-field"
+        ref="username"
+        v-model="username"
+        name="username"
+        required
+        type="text"
+        autofocus
+        :placeholder="labels.usernamePlaceholder"
+      >
     </div>
     <div class="required field">
       <label for="email-field"><translate translate-context="Content/*/*/Noun">E-mail address</translate></label>
       <input
-      id="email-field"
-      ref="email"
-      name="email"
-      required
-      type="email"
-      :placeholder="labels.emailPlaceholder"
-      v-model="email">
+        id="email-field"
+        ref="email"
+        v-model="email"
+        name="email"
+        required
+        type="email"
+        :placeholder="labels.emailPlaceholder"
+      >
     </div>
     <div class="required field">
       <label for="password-field"><translate translate-context="*/*/*">Password</translate></label>
-      <password-input field-id="password-field" v-model="password" />
+      <password-input
+        v-model="password"
+        field-id="password-field"
+      />
     </div>
-    <div class="required field" v-if="!$store.state.instance.settings.users.registration_enabled.value">
+    <div
+      v-if="!$store.state.instance.settings.users.registration_enabled.value"
+      class="required field"
+    >
       <label for="invitation-code"><translate translate-context="Content/*/Input.Label">Invitation code</translate></label>
       <input
-      id="invitation-code"
-      required
-      type="text"
-      name="invitation"
-      :placeholder="labels.placeholder"
-      v-model="invitation">
+        id="invitation-code"
+        v-model="invitation"
+        required
+        type="text"
+        name="invitation"
+        :placeholder="labels.placeholder"
+      >
     </div>
     <template v-if="signupRequiresApproval && formCustomization && formCustomization.fields && formCustomization.fields.length > 0">
-      <div :class="[{required: field.required}, 'field']" v-for="(field, idx) in formCustomization.fields" :key="idx">
+      <div
+        v-for="(field, idx) in formCustomization.fields"
+        :key="idx"
+        :class="[{required: field.required}, 'field']"
+      >
         <label :for="`custom-field-${idx}`">{{ field.label }}</label>
         <textarea
           v-if="field.input_type === 'long_text'"
           :id="`custom-field-${idx}`"
           :value="customFields[field.label]"
           :required="field.required"
-          @input="$set(customFields, field.label, $event.target.value)" rows="5"></textarea>
-        <input v-else :id="`custom-field-${idx}`" type="text" :value="customFields[field.label]" :required="field.required" @input="$set(customFields, field.label, $event.target.value)">
+          rows="5"
+          @input="$set(customFields, field.label, $event.target.value)"
+        />
+        <input
+          v-else
+          :id="`custom-field-${idx}`"
+          type="text"
+          :value="customFields[field.label]"
+          :required="field.required"
+          @input="$set(customFields, field.label, $event.target.value)"
+        >
       </div>
     </template>
-    <button :class="['ui', buttonClasses, {'loading': isLoading}, ' right floated button']" type="submit">
-      <translate translate-context="Content/Signup/Button.Label">Create my account</translate>
+    <button
+      :class="['ui', buttonClasses, {'loading': isLoading}, ' right floated button']"
+      type="submit"
+    >
+      <translate translate-context="Content/Signup/Button.Label">
+        Create my account
+      </translate>
     </button>
   </form>
 </template>
 
 <script>
-import axios from "axios"
-import logger from "@/logging"
+import axios from 'axios'
+import logger from '@/logging'
 
-import LoginForm from "@/components/auth/LoginForm"
-import PasswordInput from "@/components/forms/PasswordInput"
+import LoginForm from '@/components/auth/LoginForm'
+import PasswordInput from '@/components/forms/PasswordInput'
 
 export default {
-  props: {
-    defaultInvitation: { type: String, required: false, default: null },
-    next: { type: String, default: "/" },
-    buttonClasses: { type: String, default: "success" },
-    customization: { type: Object, default: null},
-    fetchDescriptionHtml: { type: Boolean, default: false},
-    fetchDescriptionHtml: { type: Boolean, default: false},
-    signupApprovalEnabled: {type: Boolean, default: null, required: false},
-  },
   components: {
     LoginForm,
-    PasswordInput,
+    PasswordInput
+  },
+  props: {
+    defaultInvitation: { type: String, required: false, default: null },
+    next: { type: String, default: '/' },
+    buttonClasses: { type: String, default: 'success' },
+    customization: { type: Object, default: null },
+    fetchDescriptionHtml: { type: Boolean, default: false },
+    signupApprovalEnabled: { type: Boolean, default: null, required: false }
   },
-  data() {
+  data () {
     return {
-      username: "",
-      email: "",
-      password: "",
+      username: '',
+      email: '',
+      password: '',
       isLoadingInstanceSetting: true,
       errors: [],
       isLoading: false,
       invitation: this.defaultInvitation,
       customFields: {},
-      submitted: false,
+      submitted: false
     }
   },
-  created() {
-    let self = this
-    this.$store.dispatch("instance/fetchSettings", {
-      callback: function() {
-        self.isLoadingInstanceSetting = false
-      }
-    })
-  },
   computed: {
-    labels() {
-      let placeholder = this.$pgettext(
-        "Content/Signup/Form/Placeholder",
-        "Enter your invitation code (case insensitive)"
+    labels () {
+      const placeholder = this.$pgettext(
+        'Content/Signup/Form/Placeholder',
+        'Enter your invitation code (case insensitive)'
       )
-      let usernamePlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your username")
-      let emailPlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your e-mail address")
+      const usernamePlaceholder = this.$pgettext('Content/Signup/Form/Placeholder', 'Enter your username')
+      const emailPlaceholder = this.$pgettext('Content/Signup/Form/Placeholder', 'Enter your e-mail address')
       return {
         usernamePlaceholder,
         emailPlaceholder,
@@ -152,22 +209,30 @@ export default {
       return this.signupApprovalEnabled
     }
   },
+  created () {
+    const self = this
+    this.$store.dispatch('instance/fetchSettings', {
+      callback: function () {
+        self.isLoadingInstanceSetting = false
+      }
+    })
+  },
   methods: {
-    submit() {
-      var self = this
+    submit () {
+      const self = this
       self.isLoading = true
       this.errors = []
-      var payload = {
+      const payload = {
         username: this.username,
         password1: this.password,
         password2: this.password,
         email: this.email,
         invitation: this.invitation,
-        request_fields: this.customFields,
+        request_fields: this.customFields
       }
-      return axios.post("auth/registration/", payload).then(
+      return axios.post('auth/registration/', payload).then(
         response => {
-          logger.default.info("Successfully created account")
+          logger.default.info('Successfully created account')
           self.submitted = true
           self.isLoading = false
         },
diff --git a/front/src/components/auth/SubsonicTokenForm.vue b/front/src/components/auth/SubsonicTokenForm.vue
index 4659bfac7..fce863249 100644
--- a/front/src/components/auth/SubsonicTokenForm.vue
+++ b/front/src/components/auth/SubsonicTokenForm.vue
@@ -1,61 +1,144 @@
 <template>
-  <form class="ui form" @submit.prevent="requestNewToken()">
-    <h2><translate translate-context="Content/Settings/Title">Subsonic API password</translate></h2>
-    <p class="ui message" v-if="!subsonicEnabled">
-      <translate translate-context="Content/Settings/Paragraph">The Subsonic API is not available on this Funkwhale instance.</translate>
+  <form
+    class="ui form"
+    @submit.prevent="requestNewToken()"
+  >
+    <h2>
+      <translate translate-context="Content/Settings/Title">
+        Subsonic API password
+      </translate>
+    </h2>
+    <p
+      v-if="!subsonicEnabled"
+      class="ui message"
+    >
+      <translate translate-context="Content/Settings/Paragraph">
+        The Subsonic API is not available on this Funkwhale instance.
+      </translate>
     </p>
     <p>
-      <translate translate-context="Content/Settings/Paragraph'">Funkwhale is compatible with other music players that support the Subsonic API.</translate>&nbsp;<translate translate-context="Content/Settings/Paragraph">You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.</translate>
+      <translate translate-context="Content/Settings/Paragraph'">
+        Funkwhale is compatible with other music players that support the Subsonic API.
+      </translate>&nbsp;<translate translate-context="Content/Settings/Paragraph">
+        You can use those to enjoy your playlist and music in offline mode, on your smartphone or tablet, for instance.
+      </translate>
     </p>
     <p>
-      <translate translate-context="Content/Settings/Paragraph">However, accessing Funkwhale from those clients requires a separate password you can set below.</translate>
+      <translate translate-context="Content/Settings/Paragraph">
+        However, accessing Funkwhale from those clients requires a separate password you can set below.
+      </translate>
     </p>
-    <p><a href="https://docs.funkwhale.audio/users/apps.html#subsonic-compatible-clients" target="_blank">
-      <translate translate-context="Content/Settings/Link">Discover how to use Funkwhale from other apps</translate>
-    </a></p>
-    <div v-if="success" class="ui positive message">
-      <h4 class="header">{{ successMessage }}</h4>
+    <p>
+      <a
+        href="https://docs.funkwhale.audio/users/apps.html#subsonic-compatible-clients"
+        target="_blank"
+      >
+        <translate translate-context="Content/Settings/Link">Discover how to use Funkwhale from other apps</translate>
+      </a>
+    </p>
+    <div
+      v-if="success"
+      class="ui positive message"
+    >
+      <h4 class="header">
+        {{ successMessage }}
+      </h4>
     </div>
-    <div v-if="subsonicEnabled && errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/*/Error message.Title">Error</translate></h4>
+    <div
+      v-if="subsonicEnabled && errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/*/Error message.Title">
+          Error
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
     <template v-if="subsonicEnabled">
-      <div v-if="token" class="field">
-        <label for="subsonic-password" class="visually-hidden">{{ labels.subsonicField }}</label>
+      <div
+        v-if="token"
+        class="field"
+      >
+        <label
+          for="subsonic-password"
+          class="visually-hidden"
+        >{{ labels.subsonicField }}</label>
         <password-input
-          field-id="subsonic-password"
           ref="passwordInput"
-          v-model="token"
           :key="token"
+          v-model="token"
+          field-id="subsonic-password"
           :copy-button="true"
-          :default-show="showToken"/>
+          :default-show="showToken"
+        />
       </div>
       <dangerous-button
         v-if="token"
         :class="['ui', {'loading': isLoading}, 'button']"
-        :action="requestNewToken">
-        <translate translate-context="*/Settings/Button.Label/Verb">Request a new password</translate>
-        <p slot="modal-header"><translate translate-context="Popup/Settings/Title">Request a new Subsonic API password?</translate></p>
-        <p slot="modal-content"><translate translate-context="Popup/Settings/Paragraph">This will log you out from existing devices that use the current password.</translate></p>
-        <div slot="modal-confirm"><translate translate-context="*/Settings/Button.Label/Verb">Request a new password</translate></div>
+        :action="requestNewToken"
+      >
+        <translate translate-context="*/Settings/Button.Label/Verb">
+          Request a new password
+        </translate>
+        <p slot="modal-header">
+          <translate translate-context="Popup/Settings/Title">
+            Request a new Subsonic API password?
+          </translate>
+        </p>
+        <p slot="modal-content">
+          <translate translate-context="Popup/Settings/Paragraph">
+            This will log you out from existing devices that use the current password.
+          </translate>
+        </p>
+        <div slot="modal-confirm">
+          <translate translate-context="*/Settings/Button.Label/Verb">
+            Request a new password
+          </translate>
+        </div>
       </dangerous-button>
       <button
         v-else
         color=""
         :class="['ui', {'loading': isLoading}, 'button']"
-        @click="requestNewToken"><translate translate-context="Content/Settings/Button.Label/Verb">Request a password</translate></button>
-        <dangerous-button
-          v-if="token"
-          :class="['ui', {'loading': isLoading}, 'warning', 'button']"
-          :action="disable">
-          <translate translate-context="Content/Settings/Button.Label/Verb">Disable Subsonic access</translate>
-          <p slot="modal-header"><translate translate-context="Popup/Settings/Title">Disable Subsonic API access?</translate></p>
-          <p slot="modal-content"><translate translate-context="Popup/Settings/Paragraph">This will completely disable access to the Subsonic API using from account.</translate></p>
-          <div slot="modal-confirm"><translate translate-context="Popup/Settings/Button.Label">Disable access</translate></div>
-        </dangerous-button>
+        @click="requestNewToken"
+      >
+        <translate translate-context="Content/Settings/Button.Label/Verb">
+          Request a password
+        </translate>
+      </button>
+      <dangerous-button
+        v-if="token"
+        :class="['ui', {'loading': isLoading}, 'warning', 'button']"
+        :action="disable"
+      >
+        <translate translate-context="Content/Settings/Button.Label/Verb">
+          Disable Subsonic access
+        </translate>
+        <p slot="modal-header">
+          <translate translate-context="Popup/Settings/Title">
+            Disable Subsonic API access?
+          </translate>
+        </p>
+        <p slot="modal-content">
+          <translate translate-context="Popup/Settings/Paragraph">
+            This will completely disable access to the Subsonic API using from account.
+          </translate>
+        </p>
+        <div slot="modal-confirm">
+          <translate translate-context="Popup/Settings/Button.Label">
+            Disable access
+          </translate>
+        </div>
+      </dangerous-button>
     </template>
   </form>
 </template>
@@ -78,6 +161,16 @@ export default {
       showToken: false
     }
   },
+  computed: {
+    subsonicEnabled () {
+      return this.$store.state.instance.settings.subsonic.enabled.value
+    },
+    labels () {
+      return {
+        subsonicField: this.$pgettext('Content/Password/Input.label', 'Your subsonic API password')
+      }
+    }
+  },
   created () {
     this.fetchToken()
   },
@@ -86,10 +179,10 @@ export default {
       this.success = false
       this.errors = []
       this.isLoading = true
-      let self = this
-      let url = `users/${this.$store.state.auth.username}/subsonic-token/`
+      const self = this
+      const url = `users/${this.$store.state.auth.username}/subsonic-token/`
       return axios.get(url).then(response => {
-        self.token = response.data['subsonic_api_token']
+        self.token = response.data.subsonic_api_token
         self.isLoading = false
       }, error => {
         self.isLoading = false
@@ -101,11 +194,11 @@ export default {
       this.success = false
       this.errors = []
       this.isLoading = true
-      let self = this
-      let url = `users/${this.$store.state.auth.username}/subsonic-token/`
+      const self = this
+      const url = `users/${this.$store.state.auth.username}/subsonic-token/`
       return axios.post(url, {}).then(response => {
         self.showToken = true
-        self.token = response.data['subsonic_api_token']
+        self.token = response.data.subsonic_api_token
         self.isLoading = false
         self.success = true
       }, error => {
@@ -118,8 +211,8 @@ export default {
       this.success = false
       this.errors = []
       this.isLoading = true
-      let self = this
-      let url = `users/${this.$store.state.auth.username}/subsonic-token/`
+      const self = this
+      const url = `users/${this.$store.state.auth.username}/subsonic-token/`
       return axios.delete(url).then(response => {
         self.isLoading = false
         self.token = null
@@ -129,16 +222,6 @@ export default {
         self.errors = error.backendErrors
       })
     }
-  },
-  computed: {
-    subsonicEnabled () {
-      return this.$store.state.instance.settings.subsonic.enabled.value
-    },
-    labels () {
-      return {
-        subsonicField: this.$pgettext("Content/Password/Input.label", "Your subsonic API password")
-      }
-    }
   }
 }
 </script>
diff --git a/front/src/components/channels/AlbumForm.vue b/front/src/components/channels/AlbumForm.vue
index 79044de78..1e042b5cd 100644
--- a/front/src/components/channels/AlbumForm.vue
+++ b/front/src/components/channels/AlbumForm.vue
@@ -1,16 +1,35 @@
 <template>
-  <form @submit.stop.prevent :class="['ui', {loading: isLoading}, 'form']">
-    <div v-if="errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/*/Error message.Title">Error while creating</translate></h4>
+  <form
+    :class="['ui', {loading: isLoading}, 'form']"
+    @submit.stop.prevent
+  >
+    <div
+      v-if="errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/*/Error message.Title">
+          Error while creating
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
     <div class="ui required field">
       <label for="album-title">
         <translate translate-context="*/*/*/Noun">Title</translate>
       </label>
-      <input type="text" v-model="values.title">
+      <input
+        v-model="values.title"
+        type="text"
+      >
     </div>
   </form>
 </template>
@@ -18,17 +37,17 @@
 import axios from 'axios'
 
 export default {
+  components: {},
   props: {
-    channel: {type: Object, required: true},
+    channel: { type: Object, required: true }
   },
-  components: {},
   data () {
     return {
       errors: [],
       isLoading: false,
       values: {
-        title: "",
-      },
+        title: ''
+      }
     }
   },
   computed: {
@@ -36,20 +55,28 @@ export default {
       return this.values.title.length > 0
     }
   },
+  watch: {
+    submittable (v) {
+      this.$emit('submittable', v)
+    },
+    isLoading (v) {
+      this.$emit('loading', v)
+    }
+  },
   methods: {
 
     submit () {
-      let self = this
+      const self = this
       self.isLoading = true
       self.errors = []
-      let payload = {
+      const payload = {
         ...this.values,
-        artist: this.channel.artist.id,
+        artist: this.channel.artist.id
       }
       return axios.post('albums/', payload).then(
         response => {
           self.isLoading = false
-          self.$emit("created")
+          self.$emit('created')
         },
         error => {
           self.errors = error.backendErrors
@@ -57,14 +84,6 @@ export default {
         }
       )
     }
-  },
-  watch: {
-    submittable (v) {
-      this.$emit("submittable", v)
-    },
-    isLoading (v) {
-      this.$emit("loading", v)
-    }
   }
 }
 </script>
diff --git a/front/src/components/channels/AlbumModal.vue b/front/src/components/channels/AlbumModal.vue
index 5dae29156..cf50cc031 100644
--- a/front/src/components/channels/AlbumModal.vue
+++ b/front/src/components/channels/AlbumModal.vue
@@ -1,21 +1,47 @@
 <template>
-  <modal class="small" :show.sync="show">
+  <modal
+    class="small"
+    :show.sync="show"
+  >
     <h4 class="header">
-      <translate key="1" v-if="channel.content_category === 'podcasts'" translate-context="Popup/Channels/Title/Verb">New series</translate>
-      <translate key="2" v-else translate-context="Popup/Channels/Title">New album</translate>
+      <translate
+        v-if="channel.content_category === 'podcasts'"
+        key="1"
+        translate-context="Popup/Channels/Title/Verb"
+      >
+        New series
+      </translate>
+      <translate
+        v-else
+        key="2"
+        translate-context="Popup/Channels/Title"
+      >
+        New album
+      </translate>
     </h4>
     <div class="scrolling content">
       <channel-album-form
         ref="albumForm"
+        :channel="channel"
         @loading="isLoading = $event"
         @submittable="submittable = $event"
         @created="$emit('created', $event)"
-        :channel="channel"></channel-album-form>
+      />
     </div>
     <div class="actions">
-      <button class="ui basic cancel button"><translate translate-context="*/*/Button.Label/Verb">Cancel</translate></button>
-      <button :class="['ui', 'primary', {loading: isLoading}, 'button']" :disabled="!submittable" @click.stop.prevent="$refs.albumForm.submit()">
-        <translate translate-context="*/*/Button.Label">Create</translate>
+      <button class="ui basic cancel button">
+        <translate translate-context="*/*/Button.Label/Verb">
+          Cancel
+        </translate>
+      </button>
+      <button
+        :class="['ui', 'primary', {loading: isLoading}, 'button']"
+        :disabled="!submittable"
+        @click.stop.prevent="$refs.albumForm.submit()"
+      >
+        <translate translate-context="*/*/Button.Label">
+          Create
+        </translate>
       </button>
     </div>
   </modal>
@@ -26,16 +52,16 @@ import Modal from '@/components/semantic/Modal'
 import ChannelAlbumForm from '@/components/channels/AlbumForm'
 
 export default {
-  props: ['channel'],
   components: {
     Modal,
     ChannelAlbumForm
   },
+  props: { channel: { type: Object, required: true } },
   data () {
     return {
       isLoading: false,
       submittable: false,
-      show: false,
+      show: false
     }
   },
   watch: {
diff --git a/front/src/components/channels/AlbumSelect.vue b/front/src/components/channels/AlbumSelect.vue
index 7b3770469..a5fdf91da 100644
--- a/front/src/components/channels/AlbumSelect.vue
+++ b/front/src/components/channels/AlbumSelect.vue
@@ -1,15 +1,41 @@
 <template>
   <div>
     <label for="album-dropdown">
-      <translate v-if="channel && channel.artist.content_category === 'podcast'" key="1" translate-context="*/*/*">Series</translate>
-      <translate v-else key="2" translate-context="*/*/*">Album</translate>
+      <translate
+        v-if="channel && channel.artist.content_category === 'podcast'"
+        key="1"
+        translate-context="*/*/*"
+      >Series</translate>
+      <translate
+        v-else
+        key="2"
+        translate-context="*/*/*"
+      >Album</translate>
     </label>
-    <select id="album-dropdown" :value="value" @input="$emit('input', $event.target.value)" class="ui search normal dropdown">
+    <select
+      id="album-dropdown"
+      :value="value"
+      class="ui search normal dropdown"
+      @input="$emit('input', $event.target.value)"
+    >
       <option value="">
-        <translate translate-context="*/*/*">None</translate>
+        <translate translate-context="*/*/*">
+          None
+        </translate>
       </option>
-      <option v-for="album in albums" :key="album.id" :value="album.id">
-        {{ album.title }} (<translate translate-context="*/*/*" :translate-params="{count: album.tracks_count}" :translate-n="album.tracks_count" translate-plural="%{ count } tracks">%{ count } track</translate>)
+      <option
+        v-for="album in albums"
+        :key="album.id"
+        :value="album.id"
+      >
+        {{ album.title }} (<translate
+          translate-context="*/*/*"
+          :translate-params="{count: album.tracks_count}"
+          :translate-n="album.tracks_count"
+          translate-plural="%{ count } tracks"
+        >
+          %{ count } track
+        </translate>)
       </option>
     </select>
   </div>
@@ -18,11 +44,19 @@
 import axios from 'axios'
 
 export default {
-  props: ['value', 'channel'],
+  props: {
+    value: { type: String, required: true },
+    channel: { type: Object, required: true }
+  },
   data () {
     return {
       albums: [],
-      isLoading: false,
+      isLoading: false
+    }
+  },
+  watch: {
+    async channel () {
+      await this.fetchData()
     }
   },
   async created () {
@@ -35,14 +69,9 @@ export default {
         return
       }
       this.isLoading = true
-      let response = await axios.get('albums/', {params: {artist: this.channel.artist.id, include_channels: 'true'}})
+      const response = await axios.get('albums/', { params: { artist: this.channel.artist.id, include_channels: 'true' } })
       this.albums = response.data.results
       this.isLoading = false
-    },
-  },
-  watch: {
-    async channel () {
-      await this.fetchData()
     }
   }
 }
diff --git a/front/src/components/channels/LicenseSelect.vue b/front/src/components/channels/LicenseSelect.vue
index 98317b16c..f5704af43 100644
--- a/front/src/components/channels/LicenseSelect.vue
+++ b/front/src/components/channels/LicenseSelect.vue
@@ -3,15 +3,36 @@
     <label for="license-dropdown">
       <translate translate-context="Content/*/*/Noun">License</translate>
     </label>
-    <select id="license-dropdown" :value="value" @input="$emit('input', $event.target.value)" class="ui search normal dropdown">
+    <select
+      id="license-dropdown"
+      :value="value"
+      class="ui search normal dropdown"
+      @input="$emit('input', $event.target.value)"
+    >
       <option value="">
-        <translate translate-context="*/*/*">None</translate>
+        <translate translate-context="*/*/*">
+          None
+        </translate>
+      </option>
+      <option
+        v-for="l in featuredLicenses"
+        :key="l.code"
+        :value="l.code"
+      >
+        {{ l.name }}
       </option>
-      <option v-for="l in featuredLicenses" :key="l.code" :value="l.code">{{ l.name }}</option>
     </select>
-    <p class="help" v-if="value">
-      <div class="ui very small hidden divider"></div>
-      <a :href="currentLicense.url"  v-if="value" target="_blank" rel="noreferrer noopener">
+    <div class="ui very small hidden divider" />
+    <p
+      v-if="value"
+      class="help"
+    >
+      <a
+        v-if="value"
+        :href="currentLicense.url"
+        target="_blank"
+        rel="noreferrer noopener"
+      >
         <translate translate-context="Content/*/*">About this license</translate>
       </a>
     </p>
@@ -21,7 +42,7 @@
 import axios from 'axios'
 
 export default {
-  props: ['value'],
+  props: { value: { type: String, required: true } },
   data () {
     return {
       availableLicenses: [],
@@ -32,38 +53,38 @@ export default {
         'cc-by-nc-4.0',
         'cc-by-nc-sa-4.0',
         'cc-by-nc-nd-4.0',
-        'cc-by-nd-4.0',
+        'cc-by-nd-4.0'
       ],
-      isLoading: false,
+      isLoading: false
     }
   },
-  async created () {
-    await this.fetchLicenses()
-  },
   computed: {
     featuredLicenses () {
-      let self = this
+      const self = this
       return this.availableLicenses.filter((l) => {
         return self.featuredLicensesIds.indexOf(l.code) > -1
       })
     },
     currentLicense () {
-      let self = this
+      const self = this
       if (this.value) {
         return this.availableLicenses.filter((l) => {
           return l.code === self.value
         })[0]
-
       }
+      return null
     }
   },
+  async created () {
+    await this.fetchLicenses()
+  },
   methods: {
     async fetchLicenses () {
       this.isLoading = true
-      let response = await axios.get('licenses/')
+      const response = await axios.get('licenses/')
       this.availableLicenses = response.data.results
       this.isLoading = false
-    },
-  },
+    }
+  }
 }
 </script>
diff --git a/front/src/components/channels/SubscribeButton.vue b/front/src/components/channels/SubscribeButton.vue
index 3fb06be29..3d95305d8 100644
--- a/front/src/components/channels/SubscribeButton.vue
+++ b/front/src/components/channels/SubscribeButton.vue
@@ -1,20 +1,40 @@
- <template>
-  <button v-if="$store.state.auth.authenticated" @click.stop="toggle" :class="['ui', 'pink', {'inverted': isSubscribed}, {'favorited': isSubscribed}, 'icon', 'labeled', 'button']">
-    <i class="heart icon"></i>
-    <translate v-if="isSubscribed" translate-context="Content/Track/Button.Message">Unsubscribe</translate>
-    <translate v-else translate-context="Content/Track/*/Verb">Subscribe</translate>
+<template>
+  <button
+    v-if="$store.state.auth.authenticated"
+    :class="['ui', 'pink', {'inverted': isSubscribed}, {'favorited': isSubscribed}, 'icon', 'labeled', 'button']"
+    @click.stop="toggle"
+  >
+    <i class="heart icon" />
+    <translate
+      v-if="isSubscribed"
+      translate-context="Content/Track/Button.Message"
+    >
+      Unsubscribe
+    </translate>
+    <translate
+      v-else
+      translate-context="Content/Track/*/Verb"
+    >
+      Subscribe
+    </translate>
   </button>
-  <button @click="$refs.loginModal.show = true" v-else :class="['ui', 'pink', 'icon', 'labeled', 'button']">
-    <i class="heart icon"></i>
-    <translate translate-context="Content/Track/*/Verb">Subscribe</translate>
+  <button
+    v-else
+    :class="['ui', 'pink', 'icon', 'labeled', 'button']"
+    @click="$refs.loginModal.show = true"
+  >
+    <i class="heart icon" />
+    <translate translate-context="Content/Track/*/Verb">
+      Subscribe
+    </translate>
     <login-modal
       ref="loginModal"
       class="small"
-      :nextRoute='this.$route.fullPath'
-      :message='this.message.authMessage'
-      :cover='this.channel.artist.cover'
-      @created="$refs.loginModal.show = false;">
-    </login-modal>
+      :next-route="$route.fullPath"
+      :message="message.authMessage"
+      :cover="channel.artist.cover"
+      @created="$refs.loginModal.show = false;"
+    />
   </button>
 </template>
 
@@ -22,12 +42,12 @@
 import LoginModal from '@/components/common/LoginModal'
 
 export default {
-  props: {
-    channel: {type: Object},
-  },
   components: {
     LoginModal
   },
+  props: {
+    channel: { type: Object, required: true }
+  },
   computed: {
     title () {
       if (this.isSubscribed) {
@@ -40,10 +60,10 @@ export default {
       return this.$store.getters['channels/isSubscribed'](this.channel.uuid)
     },
     message () {
-      return { 
+      return {
         authMessage: this.$pgettext('Popup/Message/Paragraph', 'You need to be logged in to subscribe to this channel')
       }
-    },
+    }
   },
   methods: {
     toggle () {
@@ -56,6 +76,5 @@ export default {
     }
   }
 
-
 }
 </script>
diff --git a/front/src/components/channels/UploadForm.vue b/front/src/components/channels/UploadForm.vue
index f40fe1179..712e9627b 100644
--- a/front/src/components/channels/UploadForm.vue
+++ b/front/src/components/channels/UploadForm.vue
@@ -1,70 +1,132 @@
 <template>
-  <form @submit.stop.prevent :class="['ui', {loading: isLoadingStep1}, 'form component-file-upload']">
-    <div v-if="errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/*/Error message.Title">Error while publishing</translate></h4>
+  <form
+    :class="['ui', {loading: isLoadingStep1}, 'form component-file-upload']"
+    @submit.stop.prevent
+  >
+    <div
+      v-if="errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/*/Error message.Title">
+          Error while publishing
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
     <div :class="['ui', 'required', {hidden: step > 1}, 'field']">
       <label for="channel-dropdown">
         <translate translate-context="*/*/*">Channel</translate>
       </label>
-      <div id="channel-dropdown" class="ui search normal selection dropdown">
-        <div class="text"></div>
-        <i class="dropdown icon"></i>
+      <div
+        id="channel-dropdown"
+        class="ui search normal selection dropdown"
+      >
+        <div class="text" />
+        <i class="dropdown icon" />
       </div>
     </div>
-    <album-select v-model.number="values.album" :channel="selectedChannel" :class="['ui', {hidden: step > 1}, 'field']"></album-select>
-    <license-select v-model="values.license" :class="['ui', {hidden: step > 1}, 'field']"></license-select>
+    <album-select
+      v-model.number="values.album"
+      :channel="selectedChannel"
+      :class="['ui', {hidden: step > 1}, 'field']"
+    />
+    <license-select
+      v-model="values.license"
+      :class="['ui', {hidden: step > 1}, 'field']"
+    />
     <div :class="['ui', {hidden: step > 1}, 'message']">
       <div class="content">
         <p>
-          <i class="copyright icon"></i>
-          <translate translate-context="Content/Channels/Popup.Paragraph">Add a license to your upload to ensure some freedoms to your public.</translate>
+          <i class="copyright icon" />
+          <translate translate-context="Content/Channels/Popup.Paragraph">
+            Add a license to your upload to ensure some freedoms to your public.
+          </translate>
         </p>
       </div>
     </div>
     <template v-if="step >= 2 && step < 4">
-      <div role="alert" class="ui warning message" v-if="remainingSpace === 0">
+      <div
+        v-if="remainingSpace === 0"
+        role="alert"
+        class="ui warning message"
+      >
         <div class="content">
           <p>
-            <i class="warning icon"></i>
-            <translate translate-context="Content/Library/Paragraph">You don't have any space left to upload your files. Please contact the moderators.</translate>
+            <i class="warning icon" />
+            <translate translate-context="Content/Library/Paragraph">
+              You don't have any space left to upload your files. Please contact the moderators.
+            </translate>
           </p>
         </div>
       </div>
       <template v-else>
-        <div class="ui visible info message" v-if="step === 2 && draftUploads && draftUploads.length > 0 && includeDraftUploads === null">
+        <div
+          v-if="step === 2 && draftUploads && draftUploads.length > 0 && includeDraftUploads === null"
+          class="ui visible info message"
+        >
           <p>
-            <i class="redo icon"></i>
-            <translate translate-context="Popup/Channels/Paragraph">You have some draft uploads pending publication.</translate>
+            <i class="redo icon" />
+            <translate translate-context="Popup/Channels/Paragraph">
+              You have some draft uploads pending publication.
+            </translate>
           </p>
-          <button @click.stop.prevent="includeDraftUploads = false" class="ui basic button">
-            <translate translate-context="*/*/*">Ignore</translate>
+          <button
+            class="ui basic button"
+            @click.stop.prevent="includeDraftUploads = false"
+          >
+            <translate translate-context="*/*/*">
+              Ignore
+            </translate>
           </button>
-          <button @click.stop.prevent="includeDraftUploads = true" class="ui basic button">
-            <translate translate-context="*/*/*">Resume</translate>
+          <button
+            class="ui basic button"
+            @click.stop.prevent="includeDraftUploads = true"
+          >
+            <translate translate-context="*/*/*">
+              Resume
+            </translate>
           </button>
         </div>
-        <div v-if="uploadedFiles.length > 0" :class="[{hidden: step === 3}]">
-          <div class="channel-file" v-for="(file, idx) in uploadedFiles">
+        <div
+          v-if="uploadedFiles.length > 0"
+          :class="[{hidden: step === 3}]"
+        >
+          <div
+            v-for="(file, idx) in uploadedFiles"
+            :key="idx"
+            class="channel-file"
+          >
             <div class="content">
-              <div role="button"
+              <div
                 v-if="file.response.uuid"
-                @click.stop.prevent="selectedUploadId = file.response.uuid"
+                role="button"
                 class="ui basic icon button"
-                :title="labels.editTitle">
-                <i class="pencil icon"></i>
+                :title="labels.editTitle"
+                @click.stop.prevent="selectedUploadId = file.response.uuid"
+              >
+                <i class="pencil icon" />
               </div>
               <div
                 v-if="file.error"
-                @click.stop.prevent="selectedUploadId = file.response.uuid"
                 class="ui basic danger icon label"
-                :title="file.error">
-                <i class="warning sign icon"></i>
+                :title="file.error"
+                @click.stop.prevent="selectedUploadId = file.response.uuid"
+              >
+                <i class="warning sign icon" />
               </div>
-              <div v-else-if="file.active" class="ui active slow inline loader"></div>
+              <div
+                v-else-if="file.active"
+                class="ui active slow inline loader"
+              />
             </div>
             <h4 class="ui header">
               <template v-if="file.metadata.title">
@@ -77,20 +139,39 @@
                 <template v-if="file.response.uuid">
                   {{ file.size | humanSize }}
                   <template v-if="file.response.duration">
-                    · <human-duration :duration="file.response.duration"></human-duration>
+                    · <human-duration :duration="file.response.duration" />
                   </template>
                 </template>
                 <template v-else>
-                  <translate key="1" v-if="file.active" translate-context="Channels/*/*">Uploading</translate>
-                  <translate key="2" v-else-if="file.error" translate-context="Channels/*/*">Errored</translate>
-                  <translate key="3" v-else translate-context="Channels/*/*">Pending</translate>
+                  <translate
+                    v-if="file.active"
+                    key="1"
+                    translate-context="Channels/*/*"
+                  >
+                    Uploading
+                  </translate>
+                  <translate
+                    v-else-if="file.error"
+                    key="2"
+                    translate-context="Channels/*/*"
+                  >
+                    Errored
+                  </translate>
+                  <translate
+                    v-else
+                    key="3"
+                    translate-context="Channels/*/*"
+                  >
+                    Pending
+                  </translate>
                   · {{ file.size | humanSize }}
                   · {{ parseInt(file.progress) }}%
                 </template>
                 · <a @click.stop.prevent="remove(file)">
                   <translate translate-context="Content/Radio/Button.Label/Verb">Remove</translate>
                 </a>
-                <template v-if="file.error"> ·
+                <template v-if="file.error">
+                  ·
                   <a @click.stop.prevent="retry(file)">
                     <translate translate-context="*/*/*">Retry</translate>
                   </a>
@@ -100,20 +181,30 @@
           </div>
         </div>
         <upload-metadata-form
-          :key="selectedUploadId"
           v-if="selectedUpload"
+          :key="selectedUploadId"
           :upload="selectedUpload"
           :values="uploadImportData[selectedUploadId]"
-          @values="setDynamic('uploadImportData', selectedUploadId, $event)"></upload-metadata-form>
-        <div class="ui message" v-if="step === 2">
+          @values="setDynamic('uploadImportData', selectedUploadId, $event)"
+        />
+        <div
+          v-if="step === 2"
+          class="ui message"
+        >
           <div class="content">
             <p>
-              <i class="info icon"></i>
-              <translate translate-context="Content/Library/Paragraph" :translate-params="{extensions: $store.state.ui.supportedExtensions.join(', ')}">Supported extensions: %{ extensions }</translate>
+              <i class="info icon" />
+              <translate
+                translate-context="Content/Library/Paragraph"
+                :translate-params="{extensions: $store.state.ui.supportedExtensions.join(', ')}"
+              >
+                Supported extensions: %{ extensions }
+              </translate>
             </p>
           </div>
         </div>
         <file-upload-widget
+          ref="upload"
           :class="['ui', 'icon', 'basic', 'button', 'channels', {hidden: step === 3}]"
           :post-action="uploadUrl"
           :multiple="true"
@@ -121,21 +212,25 @@
           :drop="true"
           :extensions="$store.state.ui.supportedExtensions"
           :value="files"
-          @input="updateFiles"
           name="audio_file"
           :thread="1"
+          @input="updateFiles"
           @input-file="inputFile"
-          ref="upload">
+        >
           <div>
-            <i class="upload icon"></i>&nbsp;
-            <translate translate-context="Content/Channels/Paragraph">Drag and drop your files here or open the browser to upload your files</translate>
+            <i class="upload icon" />&nbsp;
+            <translate translate-context="Content/Channels/Paragraph">
+              Drag and drop your files here or open the browser to upload your files
+            </translate>
           </div>
-          <div class="ui very small divider"></div>
+          <div class="ui very small divider" />
           <div>
-            <translate translate-context="*/*/*">Browse…</translate>
+            <translate translate-context="*/*/*">
+              Browse…
+            </translate>
           </div>
         </file-upload-widget>
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
       </template>
     </template>
   </form>
@@ -146,31 +241,31 @@ import $ from 'jquery'
 
 import LicenseSelect from '@/components/channels/LicenseSelect'
 import AlbumSelect from '@/components/channels/AlbumSelect'
-import FileUploadWidget from "@/components/library/FileUploadWidget";
+import FileUploadWidget from '@/components/library/FileUploadWidget'
 import UploadMetadataForm from '@/components/channels/UploadMetadataForm'
 
 function setIfEmpty (obj, k, v) {
-  if (obj[k] != undefined) {
+  if (obj[k] !== undefined) {
     return
   }
   obj[k] = v
 }
 
 export default {
-  props: {
-    channel: {type: Object, default: null, required: false},
-  },
   components: {
     AlbumSelect,
     LicenseSelect,
     FileUploadWidget,
-    UploadMetadataForm,
+    UploadMetadataForm
+  },
+  props: {
+    channel: { type: Object, default: null, required: false }
   },
   data () {
     return {
       availableChannels: {
         results: [],
-        count: 0,
+        count: 0
       },
       audioMetadata: {},
       uploadData: {},
@@ -180,29 +275,22 @@ export default {
       errors: [],
       removed: [],
       includeDraftUploads: null,
-      uploadUrl: this.$store.getters['instance/absoluteUrl']("/api/v1/uploads/"),
+      uploadUrl: this.$store.getters['instance/absoluteUrl']('/api/v1/uploads/'),
       quotaStatus: null,
       isLoadingStep1: true,
       step: 1,
       values: {
         channel: (this.channel || {}).uuid,
         license: null,
-        album: null,
+        album: null
       },
-      selectedUploadId: null,
+      selectedUploadId: null
     }
   },
-  async created () {
-    this.isLoadingStep1 = true
-    let p1 = this.fetchChannels()
-    await p1
-    this.isLoadingStep1 = false
-    this.fetchQuota()
-  },
   computed: {
     labels () {
       return {
-        editTitle: this.$pgettext('Content/*/Button.Label/Verb', 'Edit'),
+        editTitle: this.$pgettext('Content/*/Button.Label/Verb', 'Edit')
 
       }
     },
@@ -210,7 +298,7 @@ export default {
       return {
         channel: this.values.channel,
         import_status: 'draft',
-        import_metadata: {license: this.values.license, album: this.values.album || null}
+        import_metadata: { license: this.values.license, album: this.values.album || null }
       }
     },
     remainingSpace () {
@@ -220,18 +308,18 @@ export default {
       return Math.max(0, this.quotaStatus.remaining - (this.uploadedSize / (1000 * 1000)))
     },
     selectedChannel () {
-      let self = this
+      const self = this
       return this.availableChannels.results.filter((c) => {
         return c.uuid === self.values.channel
       })[0]
     },
     selectedUpload () {
-      let self = this
+      const self = this
       if (!this.selectedUploadId) {
         return null
       }
-      let selected = this.uploadedFiles.filter((f) => {
-        return f.response && f.response.uuid == self.selectedUploadId
+      const selected = this.uploadedFiles.filter((f) => {
+        return f.response && f.response.uuid === self.selectedUploadId
       })[0]
       return {
         ...selected.response,
@@ -239,27 +327,24 @@ export default {
       }
     },
     uploadedFilesById () {
-      let data = {}
+      const data = {}
       this.uploadedFiles.forEach((u) => {
         data[u.response.uuid] = u
       })
       return data
     },
     uploadedFiles () {
-      let self = this
-      self.uploadData
-      self.audioMetadata
-      let files = this.files.map((f) => {
-        let data = {
+      const self = this
+      const files = this.files.map((f) => {
+        const data = {
           ...f,
           _fileObj: f,
           metadata: {}
         }
-        let metadata = {}
         if (f.response && f.response.uuid) {
-          let uploadImportMetadata = self.uploadImportData[f.response.uuid] || self.uploadData[f.response.uuid].import_metadata
+          const uploadImportMetadata = self.uploadImportData[f.response.uuid] || self.uploadData[f.response.uuid].import_metadata
           data.metadata = {
-            ...uploadImportMetadata,
+            ...uploadImportMetadata
           }
           data.removed = self.removed.indexOf(f.response.uuid) >= 0
         }
@@ -308,7 +393,7 @@ export default {
         canSubmit: !this.activeFile && this.uploadedFiles.length > 0,
         speed,
         remaining,
-        quotaStatus: this.quotaStatus,
+        quotaStatus: this.quotaStatus
       }
     },
     totalSize () {
@@ -335,44 +420,92 @@ export default {
       })[0]
     }
   },
+  watch: {
+    'availableChannels.results' () {
+      this.setupChannelsDropdown()
+    },
+    'values.channel': {
+      async handler (v) {
+        this.files = []
+        if (v) {
+          await this.fetchDraftUploads(v)
+        }
+      },
+      immediate: true
+    },
+    step: {
+      handler (value) {
+        this.$emit('step', value)
+        if (value === 2) {
+          this.selectedUploadId = null
+        }
+      },
+      immediate: true
+    },
+    async selectedUploadId (v, o) {
+      if (v) {
+        this.step = 3
+      } else {
+        this.step = 2
+      }
+      if (o) {
+        await this.patchUpload(o, { import_metadata: this.uploadImportData[o] })
+      }
+    },
+    summaryData: {
+      handler (v) {
+        this.$emit('status', v)
+      },
+      immediate: true
+
+    }
+  },
+  async created () {
+    this.isLoadingStep1 = true
+    const p1 = this.fetchChannels()
+    await p1
+    this.isLoadingStep1 = false
+    this.fetchQuota()
+  },
   methods: {
     async fetchChannels () {
-      let response = await axios.get('channels/', {params: {scope: 'me'}})
+      const response = await axios.get('channels/', { params: { scope: 'me' } })
       this.availableChannels = response.data
     },
     async patchUpload (id, data) {
-      let response = await axios.patch(`uploads/${id}/`, data)
+      const response = await axios.patch(`uploads/${id}/`, data)
       this.uploadData[id] = response.data
       this.uploadImportData[id] = response.data.import_metadata
     },
     fetchQuota () {
-      let self = this
+      const self = this
       axios.get('users/me/').then((response) => {
         self.quotaStatus = response.data.quota_status
       })
     },
     publish () {
-      let self = this
+      const self = this
       self.isLoading = true
       self.errors = []
-      let ids = this.uploadedFiles.map((f) => {
+      const ids = this.uploadedFiles.map((f) => {
         return f.response.uuid
       })
-      let payload = {
+      const payload = {
         action: 'publish',
-        objects: ids,
+        objects: ids
       }
       return axios.post('uploads/action/', payload).then(
         response => {
           self.isLoading = false
-          self.$emit("published", {
+          self.$emit('published', {
             uploads: self.uploadedFiles.map((u) => {
               return {
                 ...u.response,
-                import_status: 'pending',
+                import_status: 'pending'
               }
             }),
-            channel: self.selectedChannel})
+            channel: self.selectedChannel
+          })
         },
         error => {
           self.errors = error.backendErrors
@@ -380,32 +513,31 @@ export default {
       )
     },
     setupChannelsDropdown () {
-      let self = this
+      const self = this
       $(this.$el).find('#channel-dropdown').dropdown({
         onChange (value, text, $choice) {
           self.values.channel = value
         },
         values: this.availableChannels.results.map((c) => {
-          let d = {
+          const d = {
             name: c.artist.name,
             value: c.uuid,
-            selected: self.channel && self.channel.uuid === c.uuid,
+            selected: self.channel && self.channel.uuid === c.uuid
           }
           if (c.artist.cover && c.artist.cover.urls.medium_square_crop) {
-            let coverUrl = self.$store.getters['instance/absoluteUrl'](c.artist.cover.urls.medium_square_crop)
+            const coverUrl = self.$store.getters['instance/absoluteUrl'](c.artist.cover.urls.medium_square_crop)
             d.image = coverUrl
             if (c.artist.content_category === 'podcast') {
               d.imageClass = 'ui image'
             } else {
-              d.imageClass = "ui avatar image"
+              d.imageClass = 'ui avatar image'
             }
           } else {
-            d.icon = "user"
+            d.icon = 'user'
             if (c.artist.content_category === 'podcast') {
-              d.iconClass = "bordered icon"
+              d.iconClass = 'bordered icon'
             } else {
-              d.iconClass = "circular icon"
-
+              d.iconClass = 'circular icon'
             }
           }
           return d
@@ -413,23 +545,23 @@ export default {
       })
       $(this.$el).find('#channel-dropdown').dropdown('hide')
     },
-    inputFile(newFile, oldFile) {
+    inputFile (newFile, oldFile) {
       if (!newFile) {
         return
       }
       if (this.remainingSpace < newFile.size / (1000 * 1000)) {
         newFile.error = 'denied'
       } else {
-        this.$refs.upload.active = true;
+        this.$refs.upload.active = true
       }
     },
     fetchAudioMetadata (uuid) {
-      let self = this
+      const self = this
       self.audioMetadata[uuid] = null
       axios.get(`uploads/${uuid}/audio-file-metadata/`).then((response) => {
         self.setDynamic('audioMetadata', uuid, response.data)
-        let uploadedFile = self.uploadedFilesById[uuid]
-        if (uploadedFile._fileObj && uploadedFile.response.import_metadata.title === uploadedFile._fileObj.name.replace(/\.[^/.]+$/, "") && response.data.title) {
+        const uploadedFile = self.uploadedFilesById[uuid]
+        if (uploadedFile._fileObj && uploadedFile.response.import_metadata.title === uploadedFile._fileObj.name.replace(/\.[^/.]+$/, '') && response.data.title) {
           // replace existing title deduced from file by the one in audio file metadat, if any
           self.uploadImportData[uuid].title = response.data.title
         } else {
@@ -439,17 +571,17 @@ export default {
         setIfEmpty(self.uploadImportData[uuid], 'position', response.data.position)
         setIfEmpty(self.uploadImportData[uuid], 'tags', response.data.tags)
         setIfEmpty(self.uploadImportData[uuid], 'description', (response.data.description || {}).text)
-        self.patchUpload(uuid, {import_metadata: self.uploadImportData[uuid]})
+        self.patchUpload(uuid, { import_metadata: self.uploadImportData[uuid] })
       })
     },
     setDynamic (objName, key, data) {
       // cf https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
-      let newData = {}
+      const newData = {}
       newData[key] = data
       this[objName] = Object.assign({}, this[objName], newData)
     },
     updateFiles (value) {
-      let self = this
+      const self = this
       this.files = value
       this.files.forEach((f) => {
         if (f.response && f.response.uuid && self.audioMetadata[f.response.uuid] === undefined) {
@@ -462,9 +594,9 @@ export default {
       })
     },
     async fetchDraftUploads (channel) {
-      let self = this
+      const self = this
       this.draftUploads = null
-      let response = await axios.get('uploads', {params: {import_status: 'draft', channel: channel}})
+      const response = await axios.get('uploads', { params: { import_status: 'draft', channel: channel } })
       this.draftUploads = response.data.results
       this.draftUploads.forEach((u) => {
         self.uploadImportData[u.uuid] = u.import_metadata
@@ -479,49 +611,8 @@ export default {
       }
     },
     retry (file) {
-      this.$refs.upload.update(file, {error: '', progress: '0.00'})
-      this.$refs.upload.active = true;
-
-    }
-  },
-  watch: {
-    "availableChannels.results" () {
-      this.setupChannelsDropdown()
-    },
-    "values.channel": {
-      async handler (v) {
-        this.files = []
-        if (v) {
-          await this.fetchDraftUploads(v)
-        }
-      },
-      immediate: true,
-    },
-    step: {
-      handler (value) {
-        this.$emit('step', value)
-        if (value === 2) {
-          this.selectedUploadId = null
-        }
-      },
-      immediate: true,
-    },
-    async selectedUploadId (v, o) {
-      if (v) {
-        this.step = 3
-      } else {
-        this.step = 2
-      }
-      if (o) {
-        await this.patchUpload(o, {import_metadata: this.uploadImportData[o]})
-      }
-    },
-    summaryData: {
-      handler (v) {
-        this.$emit('status', v)
-      },
-      immediate: true,
-
+      this.$refs.upload.update(file, { error: '', progress: '0.00' })
+      this.$refs.upload.active = true
     }
   }
 }
diff --git a/front/src/components/channels/UploadMetadataForm.vue b/front/src/components/channels/UploadMetadataForm.vue
index 8824e6e48..f7535883c 100644
--- a/front/src/components/channels/UploadMetadataForm.vue
+++ b/front/src/components/channels/UploadMetadataForm.vue
@@ -4,59 +4,79 @@
       <label for="upload-title">
         <translate translate-context="*/*/*/Noun">Title</translate>
       </label>
-      <input type="text" v-model="newValues.title">
+      <input
+        v-model="newValues.title"
+        type="text"
+      >
     </div>
     <attachment-input
       v-model="newValues.cover"
       :required="false"
-      @delete="newValues.cover = null">
-      <translate translate-context="Content/Channel/*" slot="label">Track Picture</translate>
+      @delete="newValues.cover = null"
+    >
+      <translate
+        slot="label"
+        translate-context="Content/Channel/*"
+      >
+        Track Picture
+      </translate>
     </attachment-input>
-    <div class="ui small hidden divider"></div>
+    <div class="ui small hidden divider" />
     <div class="ui two fields">
       <div class="ui field">
         <label for="upload-tags">
           <translate translate-context="*/*/*/Noun">Tags</translate>
         </label>
         <tags-selector
-          v-model="newValues.tags"
           id="upload-tags"
-          :required="false"></tags-selector>
+          v-model="newValues.tags"
+          :required="false"
+        />
       </div>
       <div class="ui field">
         <label for="upload-position">
           <translate translate-context="*/*/*/Short, Noun">Position</translate>
         </label>
-        <input type="number" min="1" step="1" v-model="newValues.position">
+        <input
+          v-model="newValues.position"
+          type="number"
+          min="1"
+          step="1"
+        >
       </div>
     </div>
     <div class="ui field">
       <label for="upload-description">
         <translate translate-context="*/*/*">Description</translate>
       </label>
-      <content-form v-model="newValues.description" field-id="upload-description"></content-form>
+      <content-form
+        v-model="newValues.description"
+        field-id="upload-description"
+      />
     </div>
   </div>
 </template>
 
 <script>
-import axios from 'axios'
 import TagsSelector from '@/components/library/TagsSelector'
 import AttachmentInput from '@/components/common/AttachmentInput'
 
 export default {
-  props: ['upload', 'values'],
   components: {
     TagsSelector,
     AttachmentInput
   },
+  props: {
+    upload: { type: Object, required: true },
+    values: { type: Object, required: true }
+  },
   data () {
     return {
-      newValues: {...this.values} || this.upload.import_metadata
+      newValues: { ...this.values } || this.upload.import_metadata
     }
   },
   computed: {
-    isLoading ()  {
+    isLoading () {
       return !!this.metadata
     }
   },
@@ -66,7 +86,7 @@ export default {
         this.$emit('values', v)
       },
       immediate: true
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/channels/UploadModal.vue b/front/src/components/channels/UploadModal.vue
index 977097ea6..89acd42dd 100644
--- a/front/src/components/channels/UploadModal.vue
+++ b/front/src/components/channels/UploadModal.vue
@@ -1,60 +1,142 @@
 <template>
-  <modal class="small" @update:show="update" :show="$store.state.channels.showUploadModal">
+  <modal
+    class="small"
+    :show="$store.state.channels.showUploadModal"
+    @update:show="update"
+  >
     <h4 class="header">
-      <translate key="1" v-if="step === 1" translate-context="Popup/Channels/Title/Verb">Publish audio</translate>
-      <translate key="2" v-else-if="step === 2" translate-context="Popup/Channels/Title">Files to upload</translate>
-      <translate key="3" v-else-if="step === 3" translate-context="Popup/Channels/Title">Upload details</translate>
-      <translate key="4" v-else-if="step === 4" translate-context="Popup/Channels/Title">Processing uploads</translate>
+      <translate
+        v-if="step === 1"
+        key="1"
+        translate-context="Popup/Channels/Title/Verb"
+      >
+        Publish audio
+      </translate>
+      <translate
+        v-else-if="step === 2"
+        key="2"
+        translate-context="Popup/Channels/Title"
+      >
+        Files to upload
+      </translate>
+      <translate
+        v-else-if="step === 3"
+        key="3"
+        translate-context="Popup/Channels/Title"
+      >
+        Upload details
+      </translate>
+      <translate
+        v-else-if="step === 4"
+        key="4"
+        translate-context="Popup/Channels/Title"
+      >
+        Processing uploads
+      </translate>
     </h4>
     <div class="scrolling content">
       <channel-upload-form
         ref="uploadForm"
+        :channel="$store.state.channels.uploadModalConfig.channel"
         @step="step = $event"
         @loading="isLoading = $event"
         @published="$store.commit('channels/publish', $event)"
         @status="statusData = $event"
         @submittable="submittable = $event"
-        :channel="$store.state.channels.uploadModalConfig.channel"></channel-upload-form>
+      />
     </div>
     <div class="actions">
       <div class="left floated text left align">
         <template v-if="statusData && step >= 2">
           {{ statusInfo.join(' · ') }}
         </template>
-        <div class="ui very small hidden divider"></div>
+        <div class="ui very small hidden divider" />
         <template v-if="statusData && statusData.quotaStatus">
-          <translate translate-context="Content/Library/Paragraph">Remaining storage space:</translate>
+          <translate translate-context="Content/Library/Paragraph">
+            Remaining storage space:
+          </translate>
           {{ (statusData.quotaStatus.remaining * 1000 * 1000) - statusData.uploadedSize | humanSize }}
         </template>
       </div>
-      <div class="ui hidden clearing divider mobile-only"></div>
-      <button class="ui basic cancel button" v-if="step === 1"><translate translate-context="*/*/Button.Label/Verb">Cancel</translate></button>
-      <button class="ui basic button" v-else-if="step < 3" @click.stop.prevent="$refs.uploadForm.step -= 1"><translate translate-context="*/*/Button.Label/Verb">Previous step</translate></button>
-      <button class="ui basic button" v-else-if="step === 3" @click.stop.prevent="$refs.uploadForm.step -= 1"><translate translate-context="*/*/Button.Label/Verb">Update</translate></button>
-      <button v-if="step === 1" class="ui primary button" @click.stop.prevent="$refs.uploadForm.step += 1">
-        <translate translate-context="*/*/Button.Label">Next step</translate>
+      <div class="ui hidden clearing divider mobile-only" />
+      <button
+        v-if="step === 1"
+        class="ui basic cancel button"
+      >
+        <translate translate-context="*/*/Button.Label/Verb">
+          Cancel
+        </translate>
       </button>
-      <div class="ui primary buttons" v-if="step === 2">
+      <button
+        v-else-if="step < 3"
+        class="ui basic button"
+        @click.stop.prevent="$refs.uploadForm.step -= 1"
+      >
+        <translate translate-context="*/*/Button.Label/Verb">
+          Previous step
+        </translate>
+      </button>
+      <button
+        v-else-if="step === 3"
+        class="ui basic button"
+        @click.stop.prevent="$refs.uploadForm.step -= 1"
+      >
+        <translate translate-context="*/*/Button.Label/Verb">
+          Update
+        </translate>
+      </button>
+      <button
+        v-if="step === 1"
+        class="ui primary button"
+        @click.stop.prevent="$refs.uploadForm.step += 1"
+      >
+        <translate translate-context="*/*/Button.Label">
+          Next step
+        </translate>
+      </button>
+      <div
+        v-if="step === 2"
+        class="ui primary buttons"
+      >
         <button
           :class="['ui', 'primary button', {loading: isLoading}]"
           type="submit"
           :disabled="!statusData || !statusData.canSubmit"
-          @click.prevent.stop="$refs.uploadForm.publish">
-          <translate translate-context="*/Channels/Button.Label">Publish</translate>
+          @click.prevent.stop="$refs.uploadForm.publish"
+        >
+          <translate translate-context="*/Channels/Button.Label">
+            Publish
+          </translate>
         </button>
-        <button class="ui floating dropdown icon button" ref="dropdown" v-dropdown :disabled="!statusData || !statusData.canSubmit">
-          <i class="dropdown icon"></i>
+        <button
+          ref="dropdown"
+          v-dropdown
+          class="ui floating dropdown icon button"
+          :disabled="!statusData || !statusData.canSubmit"
+        >
+          <i class="dropdown icon" />
           <div class="menu">
             <div
               role="button"
+              class="basic item"
               @click="update(false)"
-              class="basic item">
-              <translate translate-context="Content/*/Button.Label/Verb">Finish later</translate>
+            >
+              <translate translate-context="Content/*/Button.Label/Verb">
+                Finish later
+              </translate>
             </div>
           </div>
         </button>
       </div>
-      <button class="ui basic cancel button" @click="update(false)" v-if="step === 4"><translate translate-context="*/*/Button.Label/Verb">Close</translate></button>
+      <button
+        v-if="step === 4"
+        class="ui basic cancel button"
+        @click="update(false)"
+      >
+        <translate translate-context="*/*/Button.Label/Verb">
+          Close
+        </translate>
+      </button>
     </div>
   </modal>
 </template>
@@ -62,7 +144,7 @@
 <script>
 import Modal from '@/components/semantic/Modal'
 import ChannelUploadForm from '@/components/channels/UploadForm'
-import {humanSize} from '@/filters'
+import { humanSize } from '@/filters'
 
 export default {
   components: {
@@ -74,14 +156,9 @@ export default {
       step: 1,
       isLoading: false,
       submittable: true,
-      statusData: null,
+      statusData: null
     }
   },
-  methods: {
-    update (v) {
-      this.$store.commit('channels/showUploadModal', {show: v})
-    },
-  },
   computed: {
     labels () {
       return {}
@@ -90,14 +167,14 @@ export default {
       if (!this.statusData) {
         return []
       }
-      let info = []
+      const info = []
       if (this.statusData.totalSize) {
         info.push(humanSize(this.statusData.totalSize))
       }
       if (this.statusData.totalFiles) {
-        let msg = this.$npgettext('*/*/*', '%{ count } file', '%{ count } files', this.statusData.totalFiles)
+        const msg = this.$npgettext('*/*/*', '%{ count } file', '%{ count } files', this.statusData.totalFiles)
         info.push(
-          this.$gettextInterpolate(msg, {count: this.statusData.totalFiles}),
+          this.$gettextInterpolate(msg, { count: this.statusData.totalFiles })
         )
       }
       if (this.statusData.progress) {
@@ -107,13 +184,17 @@ export default {
         info.push(`${humanSize(this.statusData.speed)}/s`)
       }
       return info
-
     }
   },
   watch: {
     '$store.state.route.path' () {
-      this.$store.commit('channels/showUploadModal', {show: false})
-    },
+      this.$store.commit('channels/showUploadModal', { show: false })
+    }
+  },
+  methods: {
+    update (v) {
+      this.$store.commit('channels/showUploadModal', { show: v })
+    }
   }
 }
 </script>
diff --git a/front/src/components/common/ActionFeedback.vue b/front/src/components/common/ActionFeedback.vue
index 160db953d..e2a343f64 100644
--- a/front/src/components/common/ActionFeedback.vue
+++ b/front/src/components/common/ActionFeedback.vue
@@ -1,32 +1,35 @@
 <template>
-  <span class="feedback" v-if="isLoading || isDone">
-    <span v-if="isLoading" :class="['ui', 'active', size, 'inline', 'loader']"></span>
-    <i v-if="isDone" :class="['success', size, 'check', 'icon']"></i>
+  <span
+    v-if="isLoading || isDone"
+    class="feedback"
+  >
+    <span
+      v-if="isLoading"
+      :class="['ui', 'active', size, 'inline', 'loader']"
+    />
+    <i
+      v-if="isDone"
+      :class="['success', size, 'check', 'icon']"
+    />
   </span>
 </template>
 
 <script>
-import {hashCode, intToRGB} from '@/utils/color'
 
 export default {
   props: {
-    isLoading: {type: Boolean, required: true},
-    size: {type: String, default: 'small'},
+    isLoading: { type: Boolean, required: true },
+    size: { type: String, default: 'small' }
   },
   data () {
     return {
       timer: null,
-      isDone: false,
-    }
-  },
-  destroyed () {
-    if (this.timer) {
-      clearTimeout(this.timer)
+      isDone: false
     }
   },
   watch: {
     isLoading (v) {
-      let self = this
+      const self = this
       if (v && this.timer) {
         clearTimeout(this.timer)
       }
@@ -36,10 +39,14 @@ export default {
         this.isDone = true
         this.timer = setTimeout(() => {
           self.isDone = false
-        }, (2000));
-
+        }, (2000))
       }
     }
+  },
+  destroyed () {
+    if (this.timer) {
+      clearTimeout(this.timer)
+    }
   }
 }
 </script>
diff --git a/front/src/components/common/ActionTable.vue b/front/src/components/common/ActionTable.vue
index 0fe89a200..68a0c8594 100644
--- a/front/src/components/common/ActionTable.vue
+++ b/front/src/components/common/ActionTable.vue
@@ -4,111 +4,188 @@
       <thead>
         <tr>
           <th colspan="1000">
-            <div v-if="refreshable" class="right floated">
+            <div
+              v-if="refreshable"
+              class="right floated"
+            >
               <span v-if="needsRefresh">
                 <translate translate-context="Content/*/Button.Help text.Paragraph">Content has been updated, click refresh to see up-to-date content</translate>
               </span>
               <button
-                @click="$emit('refresh')"
                 class="ui basic icon button"
                 :title="labels.refresh"
-                :aria-label="labels.refresh">
-                <i class="refresh icon"></i>
+                :aria-label="labels.refresh"
+                @click="$emit('refresh')"
+              >
+                <i class="refresh icon" />
               </button>
             </div>
 
-            <div class="ui small left floated form" v-if="actionUrl && actions.length > 0">
+            <div
+              v-if="actionUrl && actions.length > 0"
+              class="ui small left floated form"
+            >
               <div class="ui inline fields">
                 <div class="field">
                   <label for="actions-select"><translate translate-context="Content/*/*/Noun">Actions</translate></label>
-                  <select id="actions-select" class="ui dropdown" v-model="currentActionName">
-                    <option v-for="action in actions" :value="action.name">
+                  <select
+                    id="actions-select"
+                    v-model="currentActionName"
+                    class="ui dropdown"
+                  >
+                    <option
+                      v-for="(action, key) in actions"
+                      :key="key"
+                      :value="action.name"
+                    >
                       {{ action.label }}
                     </option>
                   </select>
                 </div>
                 <div class="field">
                   <dangerous-button
-                    v-if="selectAll || currentAction.isDangerous" :class="['ui', {disabled: checked.length === 0}, {'loading': actionLoading}, 'button']"
+                    v-if="selectAll || currentAction.isDangerous"
+                    :class="['ui', {disabled: checked.length === 0}, {'loading': actionLoading}, 'button']"
                     :confirm-color="currentAction.confirmColor || 'success'"
-                    @confirm="launchAction" :aria-label="labels.performAction">
-                    <translate translate-context="Content/*/Button.Label/Short, Verb">Go</translate>
+                    :aria-label="labels.performAction"
+                    @confirm="launchAction"
+                  >
+                    <translate translate-context="Content/*/Button.Label/Short, Verb">
+                      Go
+                    </translate>
                     <p slot="modal-header">
-                      <translate translate-context="Modal/*/Title"
+                      <translate
                         key="1"
+                        translate-context="Modal/*/Title"
                         :translate-n="affectedObjectsCount"
                         :translate-params="{count: affectedObjectsCount, action: currentActionName}"
-                        translate-plural="Do you want to launch %{ action } on %{ count } elements?">
+                        translate-plural="Do you want to launch %{ action } on %{ count } elements?"
+                      >
                         Do you want to launch %{ action } on %{ count } element?
                       </translate>
                     </p>
                     <p slot="modal-content">
-                      <template v-if="currentAction.confirmationMessage">{{ currentAction.confirmationMessage }}</template>
-                      <translate v-else translate-context="Modal/*/Paragraph">This may affect a lot of elements or have irreversible consequences, please double check this is really what you want.</translate>
+                      <template v-if="currentAction.confirmationMessage">
+                        {{ currentAction.confirmationMessage }}
+                      </template>
+                      <translate
+                        v-else
+                        translate-context="Modal/*/Paragraph"
+                      >
+                        This may affect a lot of elements or have irreversible consequences, please double check this is really what you want.
+                      </translate>
                     </p>
-                    <div :aria-label="labels.performAction" slot="modal-confirm"><translate translate-context="Modal/*/Button.Label/Short, Verb">Launch</translate></div>
+                    <div
+                      slot="modal-confirm"
+                      :aria-label="labels.performAction"
+                    >
+                      <translate translate-context="Modal/*/Button.Label/Short, Verb">
+                        Launch
+                      </translate>
+                    </div>
                   </dangerous-button>
                   <button
                     v-else
-                    @click="launchAction"
                     :disabled="checked.length === 0"
                     :aria-label="labels.performAction"
-                    :class="['ui', {disabled: checked.length === 0}, {'loading': actionLoading}, 'button']">
-                    <translate translate-context="Content/*/Button.Label/Short, Verb">Go</translate></button>
+                    :class="['ui', {disabled: checked.length === 0}, {'loading': actionLoading}, 'button']"
+                    @click="launchAction"
+                  >
+                    <translate translate-context="Content/*/Button.Label/Short, Verb">
+                      Go
+                    </translate>
+                  </button>
                 </div>
                 <div class="count field">
-                  <translate translate-context="Content/*/Paragraph"
-                    tag="span"
+                  <translate
                     v-if="selectAll"
                     key="1"
+                    translate-context="Content/*/Paragraph"
+                    tag="span"
                     :translate-n="objectsData.count"
                     :translate-params="{count: objectsData.count, total: objectsData.count}"
-                    translate-plural="All %{ count } elements selected">
+                    translate-plural="All %{ count } elements selected"
+                  >
                     All %{ count } element selected
                   </translate>
-                  <translate translate-context="Content/*/Paragraph"
-                    tag="span"
+                  <translate
                     v-else
                     key="2"
+                    translate-context="Content/*/Paragraph"
+                    tag="span"
                     :translate-n="checked.length"
                     :translate-params="{count: checked.length, total: objectsData.count}"
-                    translate-plural="%{ count } on %{ total } selected">
+                    translate-plural="%{ count } on %{ total } selected"
+                  >
                     %{ count } on %{ total } selected
                   </translate>
                   <template v-if="currentAction.allowAll && checkable.length > 0 && checkable.length === checked.length">
-                    <a @click.prevent="selectAll = true" v-if="!selectAll" href="">
-                      <translate translate-context="Content/*/Link/Verb"
+                    <a
+                      v-if="!selectAll"
+                      href=""
+                      @click.prevent="selectAll = true"
+                    >
+                      <translate
                         key="3"
+                        translate-context="Content/*/Link/Verb"
                         :translate-n="objectsData.count"
                         :translate-params="{total: objectsData.count}"
-                        translate-plural="Select all %{ total } elements">
+                        translate-plural="Select all %{ total } elements"
+                      >
                         Select one element
                       </translate>
                     </a>
-                    <a @click.prevent="selectAll = false" v-else href="">
-                      <translate translate-context="Content/*/Link/Verb" key="4">Select only current page</translate>
+                    <a
+                      v-else
+                      href=""
+                      @click.prevent="selectAll = false"
+                    >
+                      <translate
+                        key="4"
+                        translate-context="Content/*/Link/Verb"
+                      >Select only current page</translate>
                     </a>
                   </template>
                 </div>
               </div>
-              <div v-if="actionErrors.length > 0" role="alert" class="ui negative message">
-                <h4 class="header"><translate translate-context="Content/*/Error message/Header">Error while applying action</translate></h4>
+              <div
+                v-if="actionErrors.length > 0"
+                role="alert"
+                class="ui negative message"
+              >
+                <h4 class="header">
+                  <translate translate-context="Content/*/Error message/Header">
+                    Error while applying action
+                  </translate>
+                </h4>
                 <ul class="list">
-                  <li v-for="error in actionErrors">{{ error }}</li>
+                  <li
+                    v-for="(error, key) in actionErrors"
+                    :key="key"
+                  >
+                    {{ error }}
+                  </li>
                 </ul>
               </div>
-              <div v-if="actionResult" class="ui positive message">
+              <div
+                v-if="actionResult"
+                class="ui positive message"
+              >
                 <p>
-                  <translate translate-context="Content/*/Paragraph"
+                  <translate
+                    translate-context="Content/*/Paragraph"
                     :translate-n="actionResult.updated"
                     :translate-params="{count: actionResult.updated, action: actionResult.action}"
-                    translate-plural="Action %{ action } was launched successfully on %{ count } elements">
+                    translate-plural="Action %{ action } was launched successfully on %{ count } elements"
+                  >
                     Action %{ action } was launched successfully on %{ count } element
                   </translate>
                 </p>
 
-                <slot name="action-success-footer" :result="actionResult">
-                </slot>
+                <slot
+                  name="action-success-footer"
+                  :result="actionResult"
+                />
               </div>
             </div>
           </th>
@@ -118,26 +195,37 @@
             <div class="ui checkbox">
               <input
                 type="checkbox"
-                @change="toggleCheckAll"
                 :aria-label="labels.selectAllItems"
                 :disabled="checkable.length === 0"
-                :checked="checkable.length > 0 && checked.length === checkable.length">
+                :checked="checkable.length > 0 && checked.length === checkable.length"
+                @change="toggleCheckAll"
+              >
             </div>
           </th>
-          <slot name="header-cells"></slot>
+          <slot name="header-cells" />
         </tr>
       </thead>
       <tbody v-if="objectsData.count > 0">
-        <tr v-for="(obj, index) in objects">
-          <td v-if="actions.length > 0" class="collapsing">
+        <tr
+          v-for="(obj, index) in objects"
+          :key="index"
+        >
+          <td
+            v-if="actions.length > 0"
+            class="collapsing"
+          >
             <input
               type="checkbox"
               :aria-label="labels.selectItem"
               :disabled="checkable.indexOf(getId(obj)) === -1"
+              :checked="checked.indexOf(getId(obj)) > -1"
               @click="toggleCheck($event, getId(obj), index)"
-              :checked="checked.indexOf(getId(obj)) > -1">
+            >
           </td>
-          <slot name="row-cells" :obj="obj"></slot>
+          <slot
+            name="row-cells"
+            :obj="obj"
+          />
         </tr>
       </tbody>
     </table>
@@ -147,19 +235,19 @@
 import axios from 'axios'
 
 export default {
+  components: {},
   props: {
-    actionUrl: {type: String, required: false, default: null},
-    idField: {type: String, required: false, default: 'id'},
-    refreshable: {type: Boolean, required: false, default: false},
-    needsRefresh: {type: Boolean, required: false, default: false},
-    objectsData: {type: Object, required: true},
-    actions: {type: Array, required: true, default: () => { return [] }},
-    filters: {type: Object, required: false, default: () => { return {} }},
-    customObjects: {type: Array, required: false, default: () => { return [] }},
+    actionUrl: { type: String, required: false, default: null },
+    idField: { type: String, required: false, default: 'id' },
+    refreshable: { type: Boolean, required: false, default: false },
+    needsRefresh: { type: Boolean, required: false, default: false },
+    objectsData: { type: Object, required: true },
+    actions: { type: Array, required: true, default: () => { return [] } },
+    filters: { type: Object, required: false, default: () => { return {} } },
+    customObjects: { type: Array, required: false, default: () => { return [] } }
   },
-  components: {},
   data () {
-    let d = {
+    const d = {
       checked: [],
       actionLoading: false,
       actionResult: null,
@@ -173,6 +261,71 @@ export default {
     }
     return d
   },
+  computed: {
+    currentAction () {
+      const self = this
+      return this.actions.filter((a) => {
+        return a.name === self.currentActionName
+      })[0]
+    },
+    checkable () {
+      const self = this
+      if (!this.currentAction) {
+        return []
+      }
+      let objs = this.objectsData.results
+      const filter = this.currentAction.filterCheckable
+      if (filter) {
+        objs = objs.filter((o) => {
+          return filter(o)
+        })
+      }
+      return objs.map((o) => { return self.getId(o) })
+    },
+    objects () {
+      const self = this
+      return this.objectsData.results.map((o) => {
+        const custom = self.customObjects.filter((co) => {
+          return self.getId(co) === self.getId(o)
+        })[0]
+        if (custom) {
+          return custom
+        }
+        return o
+      })
+    },
+    labels () {
+      return {
+        refresh: this.$pgettext('Content/*/Button.Tooltip/Verb', 'Refresh table content'),
+        selectAllItems: this.$pgettext('Content/*/Select/Verb', 'Select all items'),
+        performAction: this.$pgettext('Content/*/Button.Label', 'Perform actions'),
+        selectItem: this.$pgettext('Content/*/Select/Verb', 'Select')
+      }
+    },
+    affectedObjectsCount () {
+      if (this.selectAll) {
+        return this.objectsData.count
+      }
+      return this.checked.length
+    }
+  },
+  watch: {
+    objectsData: {
+      handler () {
+        this.checked = []
+        this.selectAll = false
+      },
+      deep: true
+    },
+    currentActionName () {
+      // we update checked status as some actions have specific filters
+      // on what is checkable or not
+      const self = this
+      this.checked = this.checked.filter(r => {
+        return self.checkable.indexOf(r) > -1
+      })
+    }
+  },
   methods: {
     toggleCheckAll () {
       this.lastCheckedIndex = -1
@@ -184,7 +337,7 @@ export default {
       }
     },
     toggleCheck (event, id, index) {
-      let self = this
+      const self = this
       let affectedIds = [id]
       let newValue = null
       if (this.checked.indexOf(id) > -1) {
@@ -196,13 +349,13 @@ export default {
       }
       if (event.shiftKey && this.lastCheckedIndex > -1) {
         // we also add inbetween ids to the list of affected ids
-        let idxs = [index, this.lastCheckedIndex]
+        const idxs = [index, this.lastCheckedIndex]
         idxs.sort((a, b) => a - b)
-        let objs = this.objectsData.results.slice(idxs[0], idxs[1] + 1)
+        const objs = this.objectsData.results.slice(idxs[0], idxs[1] + 1)
         affectedIds = affectedIds.concat(objs.map((o) => { return o.id }))
       }
       affectedIds.forEach((i) => {
-        let checked = self.checked.indexOf(i) > -1
+        const checked = self.checked.indexOf(i) > -1
         if (newValue && !checked && self.checkable.indexOf(i) > -1) {
           return self.checked.push(i)
         }
@@ -213,11 +366,11 @@ export default {
       this.lastCheckedIndex = index
     },
     launchAction () {
-      let self = this
+      const self = this
       self.actionLoading = true
       self.result = null
       self.actionErrors = []
-      let payload = {
+      const payload = {
         action: this.currentActionName,
         filters: this.filters
       }
@@ -238,71 +391,6 @@ export default {
     getId (obj) {
       return obj[this.idField]
     }
-  },
-  computed: {
-    currentAction () {
-      let self = this
-      return this.actions.filter((a) => {
-        return a.name === self.currentActionName
-      })[0]
-    },
-    checkable () {
-      let self = this
-      if (!this.currentAction) {
-        return []
-      }
-      let objs = this.objectsData.results
-      let filter = this.currentAction.filterCheckable
-      if (filter) {
-        objs = objs.filter((o) => {
-          return filter(o)
-        })
-      }
-      return objs.map((o) => { return self.getId(o) })
-    },
-    objects () {
-      let self = this
-      return this.objectsData.results.map((o) => {
-        let custom = self.customObjects.filter((co) => {
-          return self.getId(co) === self.getId(o)
-        })[0]
-        if (custom) {
-          return custom
-        }
-        return o
-      })
-    },
-    labels () {
-      return {
-        refresh: this.$pgettext('Content/*/Button.Tooltip/Verb', 'Refresh table content'),
-        selectAllItems: this.$pgettext('Content/*/Select/Verb', 'Select all items'),
-        performAction: this.$pgettext('Content/*/Button.Label', 'Perform actions'),
-        selectItem: this.$pgettext('Content/*/Select/Verb', 'Select')
-      }
-    },
-    affectedObjectsCount () {
-      if (this.selectAll) {
-        return this.objectsData.count
-      }
-      return this.checked.length
-    }
-  },
-  watch: {
-    objectsData: {
-      handler () {
-        this.checked = []
-        this.selectAll = false
-      },
-      deep: true
-    },
-    currentActionName () {
-      // we update checked status as some actions have specific filters
-      // on what is checkable or not
-      let self = this
-      this.checked = this.checked.filter(r => {
-        return self.checkable.indexOf(r) > -1
-      })
-    }
   }
 }
 </script>
diff --git a/front/src/components/common/ActorAvatar.vue b/front/src/components/common/ActorAvatar.vue
index 742271ab4..d4f2188cb 100644
--- a/front/src/components/common/ActorAvatar.vue
+++ b/front/src/components/common/ActorAvatar.vue
@@ -1,13 +1,22 @@
 <template>
-  <img alt="" v-if="actor.icon && actor.icon.urls.original" :src="actor.icon.urls.medium_square_crop" class="ui avatar circular image" />
-  <span v-else :style="defaultAvatarStyle" class="ui avatar circular label">{{ actor.preferred_username[0]}}</span>
+  <img
+    v-if="actor.icon && actor.icon.urls.original"
+    alt=""
+    :src="actor.icon.urls.medium_square_crop"
+    class="ui avatar circular image"
+  >
+  <span
+    v-else
+    :style="defaultAvatarStyle"
+    class="ui avatar circular label"
+  >{{ actor.preferred_username[0] }}</span>
 </template>
 
 <script>
-import {hashCode, intToRGB} from '@/utils/color'
+import { hashCode, intToRGB } from '@/utils/color'
 
 export default {
-  props: ['actor'],
+  props: { actor: { type: Object, required: true } },
   computed: {
     actorColor () {
       return intToRGB(hashCode(this.actor.full_username))
diff --git a/front/src/components/common/ActorLink.vue b/front/src/components/common/ActorLink.vue
index 81199e7ef..a30c717ca 100644
--- a/front/src/components/common/ActorLink.vue
+++ b/front/src/components/common/ActorLink.vue
@@ -1,29 +1,33 @@
 <template>
-  <router-link :to="url" :title="actor.full_username">
-    <template v-if="avatar"><actor-avatar :actor="actor" /><span>&nbsp;</span></template><slot>{{ repr | truncate(truncateLength) }}</slot>
+  <router-link
+    :to="url"
+    :title="actor.full_username"
+  >
+    <template v-if="avatar">
+      <actor-avatar :actor="actor" /><span>&nbsp;</span>
+    </template><slot>{{ repr | truncate(truncateLength) }}</slot>
   </router-link>
 </template>
 
 <script>
-import {hashCode, intToRGB} from '@/utils/color'
 
 export default {
   props: {
-    actor: {type: Object},
-    avatar: {type: Boolean, default: true},
-    admin: {type: Boolean, default: false},
-    displayName: {type: Boolean, default: false},
-    truncateLength: {type: Number, default: 30},
+    actor: { type: Object, required: true },
+    avatar: { type: Boolean, default: true },
+    admin: { type: Boolean, default: false },
+    displayName: { type: Boolean, default: false },
+    truncateLength: { type: Number, default: 30 }
   },
   computed: {
     url () {
       if (this.admin) {
-        return {name: 'manage.moderation.accounts.detail', params: {id: this.actor.full_username}}
+        return { name: 'manage.moderation.accounts.detail', params: { id: this.actor.full_username } }
       }
       if (this.actor.is_local) {
-        return {name: 'profile.overview', params: {username: this.actor.preferred_username}}
+        return { name: 'profile.overview', params: { username: this.actor.preferred_username } }
       } else {
-        return {name: 'profile.full.overview', params: {username: this.actor.preferred_username, domain: this.actor.domain}}
+        return { name: 'profile.full.overview', params: { username: this.actor.preferred_username, domain: this.actor.domain } }
       }
     },
     repr () {
diff --git a/front/src/components/common/AjaxButton.vue b/front/src/components/common/AjaxButton.vue
index 024c98515..85172b13a 100644
--- a/front/src/components/common/AjaxButton.vue
+++ b/front/src/components/common/AjaxButton.vue
@@ -1,6 +1,9 @@
 <template>
-  <button @click="ajaxCall" :class="['ui', {loading: isLoading}, 'button']">
-    <slot></slot>
+  <button
+    :class="['ui', {loading: isLoading}, 'button']"
+    @click="ajaxCall"
+  >
+    <slot />
   </button>
 </template>
 <script>
@@ -8,17 +11,17 @@ import axios from 'axios'
 
 export default {
   props: {
-    url: {type: String, required: true},
-    method: {type: String, required: true},
+    url: { type: String, required: true },
+    method: { type: String, required: true }
   },
   data () {
     return {
-      isLoading: false,
+      isLoading: false
     }
   },
   methods: {
     ajaxCall () {
-      var self = this
+      const self = this
       this.isLoading = true
       axios[this.method](this.url).then(response => {
         self.$emit('action-done', response.data)
diff --git a/front/src/components/common/AttachmentInput.vue b/front/src/components/common/AttachmentInput.vue
index cab78c85e..72bc5264f 100644
--- a/front/src/components/common/AttachmentInput.vue
+++ b/front/src/components/common/AttachmentInput.vue
@@ -1,36 +1,84 @@
 <template>
   <div class="ui form">
-    <div v-if="errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/*/Error message.Title">Your attachment cannot be saved</translate></h4>
+    <div
+      v-if="errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/*/Error message.Title">
+          Your attachment cannot be saved
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
     <div class="ui field">
       <span id="avatarLabel">
-        <slot name="label"></slot>
+        <slot name="label" />
       </span>
       <div class="ui stackable grid row">
         <div class="three wide column">
-          <img alt="" :class="['ui', imageClass, 'image']" v-if="value && value === initialValue" :src="$store.getters['instance/absoluteUrl'](`api/v1/attachments/${value}/proxy?next=medium_square_crop`)" />
-          <img alt="" :class="['ui', imageClass, 'image']" v-else-if="attachment" :src="$store.getters['instance/absoluteUrl'](`api/v1/attachments/${attachment.uuid}/proxy?next=medium_square_crop`)" />
-          <div :class="['ui', imageClass, 'static', 'large placeholder image']" v-else></div>
+          <img
+            v-if="value && value === initialValue"
+            alt=""
+            :class="['ui', imageClass, 'image']"
+            :src="$store.getters['instance/absoluteUrl'](`api/v1/attachments/${value}/proxy?next=medium_square_crop`)"
+          >
+          <img
+            v-else-if="attachment"
+            alt=""
+            :class="['ui', imageClass, 'image']"
+            :src="$store.getters['instance/absoluteUrl'](`api/v1/attachments/${attachment.uuid}/proxy?next=medium_square_crop`)"
+          >
+          <div
+            v-else
+            :class="['ui', imageClass, 'static', 'large placeholder image']"
+          />
         </div>
         <div class="eleven wide column">
           <div class="file-input">
             <label :for="attachmentId">
               <translate translate-context="*/*/*">Upload New Picture…</translate>
             </label>
-            <input class="ui input" ref="attachment" type="file" :id="attachmentId" accept="image/x-png,image/jpeg" @change="submit" />
+            <input
+              :id="attachmentId"
+              ref="attachment"
+              class="ui input"
+              type="file"
+              accept="image/x-png,image/jpeg"
+              @change="submit"
+            >
           </div>
-          <div class="ui very small hidden divider"></div>
-          <p><translate translate-context="Content/*/Paragraph">PNG or JPG. Dimensions should be between 1400x1400px and 3000x3000px. Maximum file size allowed is 5MB.</translate></p>
-          <button class="ui basic tiny button" v-if="value" @click.stop.prevent="remove(value)">
-            <translate translate-context="Content/Radio/Button.Label/Verb">Remove</translate>
+          <div class="ui very small hidden divider" />
+          <p>
+            <translate translate-context="Content/*/Paragraph">
+              PNG or JPG. Dimensions should be between 1400x1400px and 3000x3000px. Maximum file size allowed is 5MB.
+            </translate>
+          </p>
+          <button
+            v-if="value"
+            class="ui basic tiny button"
+            @click.stop.prevent="remove(value)"
+          >
+            <translate translate-context="Content/Radio/Button.Label/Verb">
+              Remove
+            </translate>
           </button>
-          <div v-if="isLoading" class="ui active inverted dimmer">
+          <div
+            v-if="isLoading"
+            class="ui active inverted dimmer"
+          >
             <div class="ui indeterminate text loader">
-              <translate translate-context="Content/*/*/Noun">Uploading file…</translate>
+              <translate translate-context="Content/*/*/Noun">
+                Uploading file…
+              </translate>
             </div>
           </div>
         </div>
@@ -43,8 +91,8 @@ import axios from 'axios'
 
 export default {
   props: {
-    value: {},
-    imageClass: {default: '', required: false}
+    value: { type: String, required: true },
+    imageClass: { type: String, default: '', required: false }
   },
   data () {
     return {
@@ -52,21 +100,29 @@ export default {
       isLoading: false,
       errors: [],
       initialValue: this.value,
-      attachmentId: Math.random().toString(36).substring(7),
+      attachmentId: Math.random().toString(36).substring(7)
+    }
+  },
+  watch: {
+    value (v) {
+      if (this.attachment && v === this.initialValue) {
+        // we had a reset to initial value
+        this.remove(this.attachment.uuid)
+      }
     }
   },
   methods: {
-    submit() {
+    submit () {
       this.isLoading = true
       this.errors = []
-      let self = this
+      const self = this
       this.file = this.$refs.attachment.files[0]
-      let formData = new FormData()
-      formData.append("file", this.file)
+      const formData = new FormData()
+      formData.append('file', this.file)
       axios
-        .post(`attachments/`, formData, {
+        .post('attachments/', formData, {
           headers: {
-            "Content-Type": "multipart/form-data"
+            'Content-Type': 'multipart/form-data'
           }
         })
         .then(
@@ -81,10 +137,10 @@ export default {
           }
         )
     },
-    remove(uuid) {
+    remove (uuid) {
       this.isLoading = true
       this.errors = []
-      let self = this
+      const self = this
       axios.delete(`attachments/${uuid}/`)
         .then(
           response => {
@@ -97,14 +153,6 @@ export default {
             self.errors = error.backendErrors
           }
         )
-    },
-  },
-  watch: {
-    value (v) {
-      if (this.attachment && v === this.initialValue) {
-        // we had a reset to initial value
-        this.remove(this.attachment.uuid)
-      }
     }
   }
 }
diff --git a/front/src/components/common/CollapseLink.vue b/front/src/components/common/CollapseLink.vue
index 072a3282b..30d6dc326 100644
--- a/front/src/components/common/CollapseLink.vue
+++ b/front/src/components/common/CollapseLink.vue
@@ -1,15 +1,27 @@
 <template>
-  <a role="button" class="collapse link" @click.prevent="$emit('input', !value)">
-    <translate v-if="isCollapsed" key="1" translate-context="*/*/Button,Label">Expand</translate>
-    <translate v-else key="2" translate-context="*/*/Button,Label">Collapse</translate>
-    <i :class="[{down: !isCollapsed}, {right: isCollapsed}, 'angle', 'icon']"></i>
+  <a
+    role="button"
+    class="collapse link"
+    @click.prevent="$emit('input', !value)"
+  >
+    <translate
+      v-if="isCollapsed"
+      key="1"
+      translate-context="*/*/Button,Label"
+    >Expand</translate>
+    <translate
+      v-else
+      key="2"
+      translate-context="*/*/Button,Label"
+    >Collapse</translate>
+    <i :class="[{down: !isCollapsed}, {right: isCollapsed}, 'angle', 'icon']" />
   </a>
 </template>
 <script>
 
 export default {
   props: {
-    value: {type: Boolean, required: true},
+    value: { type: Boolean, required: true }
   },
   computed: {
     isCollapsed () {
diff --git a/front/src/components/common/ContentForm.vue b/front/src/components/common/ContentForm.vue
index 4b1fd73c6..29f333341 100644
--- a/front/src/components/common/ContentForm.vue
+++ b/front/src/components/common/ContentForm.vue
@@ -2,48 +2,71 @@
   <div class="content-form ui segments">
     <div class="ui segment">
       <div class="ui tiny secondary pointing menu">
-        <button @click.prevent="isPreviewing = false" :class="[{active: !isPreviewing}, 'item']">
-          <translate translate-context="*/Form/Menu.item">Write</translate>
+        <button
+          :class="[{active: !isPreviewing}, 'item']"
+          @click.prevent="isPreviewing = false"
+        >
+          <translate translate-context="*/Form/Menu.item">
+            Write
+          </translate>
         </button>
-        <button @click.prevent="isPreviewing = true" :class="[{active: isPreviewing}, 'item']">
-          <translate translate-context="*/Form/Menu.item">Preview</translate>
+        <button
+          :class="[{active: isPreviewing}, 'item']"
+          @click.prevent="isPreviewing = true"
+        >
+          <translate translate-context="*/Form/Menu.item">
+            Preview
+          </translate>
         </button>
       </div>
-      <template v-if="isPreviewing" >
-
-        <div class="ui placeholder" v-if="isLoadingPreview">
+      <template v-if="isPreviewing">
+        <div
+          v-if="isLoadingPreview"
+          class="ui placeholder"
+        >
           <div class="paragraph">
-            <div class="line"></div>
-            <div class="line"></div>
-            <div class="line"></div>
-            <div class="line"></div>
+            <div class="line" />
+            <div class="line" />
+            <div class="line" />
+            <div class="line" />
           </div>
         </div>
         <p v-else-if="preview === null">
-          <translate translate-context="*/Form/Paragraph">Nothing to preview.</translate>
+          <translate translate-context="*/Form/Paragraph">
+            Nothing to preview.
+          </translate>
         </p>
-        <div v-html="preview" v-else></div>
+        <div
+          v-else
+          v-html="preview"
+        />
       </template>
       <template v-else>
         <div class="ui transparent input">
           <textarea
+            :id="fieldId"
             ref="textarea"
+            v-model="newValue"
             :name="fieldId"
-            :id="fieldId"
             :rows="rows"
-            v-model="newValue"
             :required="required"
-            :placeholder="placeholder || labels.placeholder"></textarea>
+            :placeholder="placeholder || labels.placeholder"
+          />
         </div>
-        <div class="ui very small hidden divider"></div>
+        <div class="ui very small hidden divider" />
       </template>
     </div>
     <div class="ui bottom attached segment">
-      <span :class="['right', 'floated', {'ui danger text': remainingChars < 0}]" v-if="charLimit">
+      <span
+        v-if="charLimit"
+        :class="['right', 'floated', {'ui danger text': remainingChars < 0}]"
+      >
         {{ remainingChars }}
       </span>
       <p>
-        <translate translate-context="*/Form/Paragraph">Markdown syntax is supported.</translate>
+        <translate translate-context="*/Form/Paragraph">
+          Markdown syntax is supported.
+        </translate>
       </p>
     </div>
   </div>
@@ -54,50 +77,31 @@ import axios from 'axios'
 
 export default {
   props: {
-    value: {type: String, default: ""},
-    fieldId: {type: String, default: "change-content"},
-    placeholder: {type: String, default: null},
-    autofocus: {type: Boolean, default: false},
-    charLimit: {type: Number, default: 5000, required: false},
-    rows: {type: Number, default: 5, required: false},
-    permissive: {type: Boolean, default: false},
-    required: {type: Boolean, default: false},
+    value: { type: String, default: '' },
+    fieldId: { type: String, default: 'change-content' },
+    placeholder: { type: String, default: null },
+    autofocus: { type: Boolean, default: false },
+    charLimit: { type: Number, default: 5000, required: false },
+    rows: { type: Number, default: 5, required: false },
+    permissive: { type: Boolean, default: false },
+    required: { type: Boolean, default: false }
   },
   data () {
     return {
       isPreviewing: false,
       preview: null,
       newValue: this.value,
-      isLoadingPreview: false,
-    }
-  },
-  mounted () {
-    if (this.autofocus) {
-      this.$nextTick(() => {
-        this.$refs.textarea.focus()
-      })
-    }
-  },
-  methods: {
-    async loadPreview () {
-      this.isLoadingPreview = true
-      try {
-        let response = await axios.post('text-preview/', {text: this.newValue, permissive: this.permissive})
-        this.preview = response.data.rendered
-      } catch {
-
-      }
-      this.isLoadingPreview = false
+      isLoadingPreview: false
     }
   },
   computed: {
     labels () {
       return {
-        placeholder: this.$pgettext("*/Form/Placeholder", "Write a few words here…")
+        placeholder: this.$pgettext('*/Form/Placeholder', 'Write a few words here…')
       }
     },
     remainingChars () {
-      return this.charLimit - (this.value || "").length
+      return this.charLimit - (this.value || '').length
     }
   },
   watch: {
@@ -113,7 +117,7 @@ export default {
           await this.loadPreview()
         }
       },
-      immediate: true,
+      immediate: true
     },
     async isPreviewing (v) {
       if (v && !!this.value && this.preview === null && !this.isLoadingPreview) {
@@ -125,6 +129,25 @@ export default {
         })
       }
     }
+  },
+  mounted () {
+    if (this.autofocus) {
+      this.$nextTick(() => {
+        this.$refs.textarea.focus()
+      })
+    }
+  },
+  methods: {
+    async loadPreview () {
+      this.isLoadingPreview = true
+      try {
+        const response = await axios.post('text-preview/', { text: this.newValue, permissive: this.permissive })
+        this.preview = response.data.rendered
+      } catch {
+
+      }
+      this.isLoadingPreview = false
+    }
   }
 }
 </script>
diff --git a/front/src/components/common/CopyInput.vue b/front/src/components/common/CopyInput.vue
index 0878e4edf..614b81382 100644
--- a/front/src/components/common/CopyInput.vue
+++ b/front/src/components/common/CopyInput.vue
@@ -1,21 +1,38 @@
 <template>
   <div class="ui fluid action input component-copy-input">
-    <p class="message" v-if="copied">
-      <translate translate-context="Content/*/Paragraph">Text copied to clipboard!</translate>
+    <p
+      v-if="copied"
+      class="message"
+    >
+      <translate translate-context="Content/*/Paragraph">
+        Text copied to clipboard!
+      </translate>
     </p>
-    <input :id="id" :name="id" ref="input" :value="value" type="text" readonly>
-    <button @click="copy" :class="['ui', buttonClasses, 'right', 'labeled', 'icon', 'button']">
-      <i class="copy icon"></i>
-      <translate translate-context="*/*/Button.Label/Short, Verb">Copy</translate>
+    <input
+      :id="id"
+      ref="input"
+      :name="id"
+      :value="value"
+      type="text"
+      readonly
+    >
+    <button
+      :class="['ui', buttonClasses, 'right', 'labeled', 'icon', 'button']"
+      @click="copy"
+    >
+      <i class="copy icon" />
+      <translate translate-context="*/*/Button.Label/Short, Verb">
+        Copy
+      </translate>
     </button>
   </div>
 </template>
 <script>
 export default {
   props: {
-    value: {type: String},
-    buttonClasses: {type: String, default: 'accent'},
-    id: {type: String, default: 'copy-input'},
+    value: { type: String, required: true },
+    buttonClasses: { type: String, default: 'accent' },
+    id: { type: String, default: 'copy-input' }
   },
   data () {
     return {
@@ -29,8 +46,8 @@ export default {
         clearTimeout(this.timeout)
       }
       this.$refs.input.select()
-      document.execCommand("Copy")
-      let self = this
+      document.execCommand('Copy')
+      const self = this
       self.copied = true
       this.timeout = setTimeout(() => {
         self.copied = false
diff --git a/front/src/components/common/DangerousButton.vue b/front/src/components/common/DangerousButton.vue
index 4ffe35c6f..25a25d2fc 100644
--- a/front/src/components/common/DangerousButton.vue
+++ b/front/src/components/common/DangerousButton.vue
@@ -1,44 +1,59 @@
 <template>
-  <button @click="showModal = true" :class="[{disabled: disabled}]" :disabled="disabled">
-    <slot></slot>
+  <button
+    :class="[{disabled: disabled}]"
+    :disabled="disabled"
+    @click="showModal = true"
+  >
+    <slot />
 
-    <modal class="small" :show.sync="showModal">
+    <modal
+      class="small"
+      :show.sync="showModal"
+    >
       <h4 class="header">
         <slot name="modal-header">
-          <translate translate-context="Modal/*/Title">Do you want to confirm this action?</translate>
+          <translate translate-context="Modal/*/Title">
+            Do you want to confirm this action?
+          </translate>
         </slot>
       </h4>
       <div class="scrolling content">
         <div class="description">
-          <slot name="modal-content"></slot>
+          <slot name="modal-content" />
         </div>
       </div>
       <div class="actions">
         <button class="ui basic cancel button">
-          <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+          <translate translate-context="*/*/Button.Label/Verb">
+            Cancel
+          </translate>
         </button>
-        <button :class="['ui', 'confirm', confirmButtonColor, 'button']" @click="confirm">
+        <button
+          :class="['ui', 'confirm', confirmButtonColor, 'button']"
+          @click="confirm"
+        >
           <slot name="modal-confirm">
-            <translate translate-context="Modal/*/Button.Label/Short, Verb">Confirm</translate>
+            <translate translate-context="Modal/*/Button.Label/Short, Verb">
+              Confirm
+            </translate>
           </slot>
         </button>
       </div>
     </modal>
   </button>
-
 </template>
 <script>
 import Modal from '@/components/semantic/Modal'
 
 export default {
-  props: {
-    action: {type: Function, required: false},
-    disabled: {type: Boolean, default: false},
-    confirmColor: {type: String, default: "danger", required: false}
-  },
   components: {
     Modal
   },
+  props: {
+    action: { type: Function, required: false, default: () => {} },
+    disabled: { type: Boolean, default: false },
+    confirmColor: { type: String, default: 'danger', required: false }
+  },
   data () {
     return {
       showModal: false
diff --git a/front/src/components/common/Duration.vue b/front/src/components/common/Duration.vue
index 4ee8ccfd1..81a0b578f 100644
--- a/front/src/components/common/Duration.vue
+++ b/front/src/components/common/Duration.vue
@@ -1,18 +1,22 @@
 <template>
   <span>
-    <translate translate-context="Content/*/Paragraph"
+    <translate
       v-if="durationData.hours > 0"
-      :translate-params="{minutes: durationData.minutes, hours: durationData.hours}">%{ hours } h %{ minutes } min</translate>
-    <translate translate-context="Content/*/Paragraph"
+      translate-context="Content/*/Paragraph"
+      :translate-params="{minutes: durationData.minutes, hours: durationData.hours}"
+    >%{ hours } h %{ minutes } min</translate>
+    <translate
       v-else
-      :translate-params="{minutes: durationData.minutes}">%{ minutes } min</translate>
+      translate-context="Content/*/Paragraph"
+      :translate-params="{minutes: durationData.minutes}"
+    >%{ minutes } min</translate>
   </span>
 </template>
 <script>
-import {secondsToObject} from '@/filters'
+import { secondsToObject } from '@/filters'
 
 export default {
-  props: ['seconds'],
+  props: { seconds: { type: Number, required: true } },
   computed: {
     durationData () {
       return secondsToObject(this.seconds)
diff --git a/front/src/components/common/EmptyState.vue b/front/src/components/common/EmptyState.vue
index 661cc6ebf..54e1b13e3 100644
--- a/front/src/components/common/EmptyState.vue
+++ b/front/src/components/common/EmptyState.vue
@@ -3,8 +3,7 @@
     <h4 class="ui header">
       <div class="content">
         <slot name="title">
-
-          <i class="search icon"></i>
+          <i class="search icon" />
           <translate translate-context="Content/*/Paragraph">
             No results were found.
           </translate>
@@ -12,8 +11,12 @@
       </div>
     </h4>
     <div class="inline center aligned text">
-      <slot></slot>
-      <button v-if="refresh" class="ui button" @click="$emit('refresh')">
+      <slot />
+      <button
+        v-if="refresh"
+        class="ui button"
+        @click="$emit('refresh')"
+      >
         <translate translate-context="Content/*/Button.Label/Short, Verb">
           Refresh
         </translate>
@@ -24,7 +27,7 @@
 <script>
 export default {
   props: {
-    refresh: {type: Boolean, default: false}
+    refresh: { type: Boolean, default: false }
   }
 }
 </script>
diff --git a/front/src/components/common/ExpandableDiv.vue b/front/src/components/common/ExpandableDiv.vue
index 2a95a11a2..54ddc7b36 100644
--- a/front/src/components/common/ExpandableDiv.vue
+++ b/front/src/components/common/ExpandableDiv.vue
@@ -3,10 +3,22 @@
     <div :class="['expandable-content', {expandable: truncated.length < content.length}, {expanded: isExpanded}]">
       <slot>{{ content }}</slot>
     </div>
-    <a v-if="truncated.length < content.length" role="button" @click.prevent="isExpanded = !isExpanded">
+    <a
+      v-if="truncated.length < content.length"
+      role="button"
+      @click.prevent="isExpanded = !isExpanded"
+    >
       <br>
-      <translate v-if="isExpanded" key="1" translate-context="*/*/Button,Label">Show less</translate>
-      <translate v-else key="2" translate-context="*/*/Button,Label">Show more</translate>
+      <translate
+        v-if="isExpanded"
+        key="1"
+        translate-context="*/*/Button,Label"
+      >Show less</translate>
+      <translate
+        v-else
+        key="2"
+        translate-context="*/*/Button,Label"
+      >Show more</translate>
     </a>
   </div>
 </template>
@@ -15,12 +27,12 @@
 
 export default {
   props: {
-    content: {type: String, required: true},
-    length: {type: Number, default: 150, required: false},
+    content: { type: String, required: true },
+    length: { type: Number, default: 150, required: false }
   },
   data () {
     return {
-      isExpanded: false,
+      isExpanded: false
     }
   },
   computed: {
diff --git a/front/src/components/common/HumanDate.vue b/front/src/components/common/HumanDate.vue
index fde04f141..5dc267adf 100644
--- a/front/src/components/common/HumanDate.vue
+++ b/front/src/components/common/HumanDate.vue
@@ -1,15 +1,21 @@
 <template>
-  <time :datetime="date" :title="date | moment">
-    <i v-if="icon" class="outline clock icon"></i>
+  <time
+    :datetime="date"
+    :title="date | moment"
+  >
+    <i
+      v-if="icon"
+      class="outline clock icon"
+    />
     {{ realDate | ago($store.state.ui.momentLocale) }}
   </time>
 </template>
 <script>
-import {mapState} from 'vuex'
+import { mapState } from 'vuex'
 export default {
   props: {
-    date: {required: true},
-    icon: {type: Boolean, required: false, default: false},
+    date: { type: String, required: true },
+    icon: { type: Boolean, required: false, default: false }
   },
   computed: {
     ...mapState({
diff --git a/front/src/components/common/HumanDuration.vue b/front/src/components/common/HumanDuration.vue
index 07b4fdd03..84ed24c79 100644
--- a/front/src/components/common/HumanDuration.vue
+++ b/front/src/components/common/HumanDuration.vue
@@ -1,13 +1,12 @@
 <template>
   <time :datetime="`${duration}s`">
-    {{ duration | duration}}
+    {{ duration | duration }}
   </time>
-
 </template>
 <script>
 export default {
   props: {
-    duration: {required: true},
-  },
+    duration: { type: Object, required: true }
+  }
 }
 </script>
diff --git a/front/src/components/common/InlineSearchBar.vue b/front/src/components/common/InlineSearchBar.vue
index 3a32b84c0..590e98961 100644
--- a/front/src/components/common/InlineSearchBar.vue
+++ b/front/src/components/common/InlineSearchBar.vue
@@ -1,13 +1,34 @@
 <template>
-  <form class="ui inline form" @submit.stop.prevent="$emit('search', value)">
+  <form
+    class="ui inline form"
+    @submit.stop.prevent="$emit('search', value)"
+  >
     <div :class="['ui', 'action', {icon: isClearable}, 'input']">
-      <label for="search-query" class="hidden">
+      <label
+        for="search-query"
+        class="hidden"
+      >
         <translate translate-context="Content/Search/Input.Label/Noun">Search</translate>
       </label>
-      <input id="search-query" name="search-query" type="text" :placeholder="placeholder || labels.searchPlaceholder" :value="value" @input="$emit('input', $event.target.value)">
-      <i v-if="isClearable" class="x link icon" :title="labels.clear" @click.stop.prevent="$emit('input', ''); $emit('search', value)"></i>
-      <button type="submit" class="ui icon basic button">
-        <i class="search icon"></i>
+      <input
+        id="search-query"
+        name="search-query"
+        type="text"
+        :placeholder="placeholder || labels.searchPlaceholder"
+        :value="value"
+        @input="$emit('input', $event.target.value)"
+      >
+      <i
+        v-if="isClearable"
+        class="x link icon"
+        :title="labels.clear"
+        @click.stop.prevent="$emit('input', ''); $emit('search', value)"
+      />
+      <button
+        type="submit"
+        class="ui icon basic button"
+      >
+        <i class="search icon" />
       </button>
     </div>
   </form>
@@ -15,14 +36,14 @@
 <script>
 export default {
   props: {
-    value: {type: String, required: true},
-    placeholder: {type: String, required: false},
+    value: { type: String, required: true },
+    placeholder: { type: String, required: false, default: '' }
   },
   computed: {
     labels () {
       return {
         searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search…'),
-        clear: this.$pgettext("Content/Library/Button.Label", 'Clear'),
+        clear: this.$pgettext('Content/Library/Button.Label', 'Clear')
       }
     },
     isClearable () {
diff --git a/front/src/components/common/LoginModal.vue b/front/src/components/common/LoginModal.vue
index 30cfb5b89..5f2d5b7c2 100644
--- a/front/src/components/common/LoginModal.vue
+++ b/front/src/components/common/LoginModal.vue
@@ -1,64 +1,81 @@
 <template>
-    <modal :show.sync="show">
-      <h4 class="header">{{ labels.header }}</h4>
-      <div v-if="cover" class="image content">
-        <div class="ui medium image">
-          <img :src="cover.urls.medium_square_crop">
-        </div>
-        <div class="description">
-          <div class="ui header">
-            {{ labels.description }}
-          </div>
-          <p>
-            {{ message }}
-          </p>
-        </div>
+  <modal :show.sync="show">
+    <h4 class="header">
+      {{ labels.header }}
+    </h4>
+    <div
+      v-if="cover"
+      class="image content"
+    >
+      <div class="ui medium image">
+        <img :src="cover.urls.medium_square_crop">
       </div>
-      <div v-else class="content">
-        <div class="ui centered header">
+      <div class="description">
+        <div class="ui header">
           {{ labels.description }}
         </div>
-        <p style="text-align: center;">
+        <p>
           {{ message }}
         </p>
       </div>
-      <div class="actions">
-        <router-link :to="{path: '/login', query: { next: nextRoute }}" class="ui labeled icon button"><i class="key icon"></i>
-          {{ labels.login }}
-        </router-link>
-        <router-link v-if="$store.state.instance.settings.users.registration_enabled.value" :to="{path: '/signup'}" class="ui labeled icon button"><i class="user icon"></i>
-          {{ labels.signup }}
-        </router-link>
+    </div>
+    <div
+      v-else
+      class="content"
+    >
+      <div class="ui centered header">
+        {{ labels.description }}
       </div>
-    </modal>
+      <p style="text-align: center;">
+        {{ message }}
+      </p>
+    </div>
+    <div class="actions">
+      <router-link
+        :to="{path: '/login', query: { next: nextRoute }}"
+        class="ui labeled icon button"
+      >
+        <i class="key icon" />
+        {{ labels.login }}
+      </router-link>
+      <router-link
+        v-if="$store.state.instance.settings.users.registration_enabled.value"
+        :to="{path: '/signup'}"
+        class="ui labeled icon button"
+      >
+        <i class="user icon" />
+        {{ labels.signup }}
+      </router-link>
+    </div>
+  </modal>
 </template>
 
 <script>
 import Modal from '@/components/semantic/Modal'
 
 export default {
-  props: {
-    nextRoute: {type: String},
-    message: {type: String},
-    cover: {type: Object},
-  },
   components: {
-    Modal,
+    Modal
   },
-  data() {
+  props: {
+    nextRoute: { type: String, required: true },
+    message: { type: String, required: true },
+    cover: { type: Object, required: true }
+  },
+  data () {
     return {
-      show: false,
+      show: false
     }
   },
   computed: {
-    labels() {
+    labels () {
       return {
-        header: this.$pgettext('Popup/Title/Noun', "Unauthenticated"),
-        login: this.$pgettext('*/*/Button.Label/Verb', "Log in"),
-        signup: this.$pgettext('*/*/Button.Label/Verb', "Sign up"),
-        description: this.$pgettext('Popup/*/Paragraph', "You don't have access!"),
+        header: this.$pgettext('Popup/Title/Noun', 'Unauthenticated'),
+        login: this.$pgettext('*/*/Button.Label/Verb', 'Log in'),
+        signup: this.$pgettext('*/*/Button.Label/Verb', 'Sign up'),
+        description: this.$pgettext('Popup/*/Paragraph', "You don't have access!")
       }
-    },
+    }
   }
 }
 
diff --git a/front/src/components/common/Message.vue b/front/src/components/common/Message.vue
index 9bf713f95..7c57e30d1 100644
--- a/front/src/components/common/Message.vue
+++ b/front/src/components/common/Message.vue
@@ -1,27 +1,27 @@
 <template>
-  <div></div>
+  <div />
 </template>
 <script>
 import $ from 'jquery'
 
 export default {
-  props: ['message'],
+  props: { message: { type: Object, required: true } },
   mounted () {
-    let self = this
-    let params = {
-      context: "#app",
+    const self = this
+    const params = {
+      context: '#app',
       message: this.message.content,
       showProgress: 'top',
-      position: "bottom right",
+      position: 'bottom right',
       progressUp: true,
       onRemove () {
-        self.$store.commit("ui/removeMessage", self.message.key)
+        self.$store.commit('ui/removeMessage', self.message.key)
       },
-      ...this.message,
+      ...this.message
     }
-    $("body").toast(params)
+    $('body').toast(params)
 
-    $(".ui.toast.visible").last().attr('role', 'alert')
+    $('.ui.toast.visible').last().attr('role', 'alert')
   }
 }
 </script>
diff --git a/front/src/components/common/RenderedDescription.vue b/front/src/components/common/RenderedDescription.vue
index b7db84d64..674591264 100644
--- a/front/src/components/common/RenderedDescription.vue
+++ b/front/src/components/common/RenderedDescription.vue
@@ -1,79 +1,113 @@
 <template>
   <div>
     <template v-if="content && !isUpdating">
-      <div v-html="html"></div>
+      <div v-html="html" />
       <template v-if="isTruncated">
-        <div class="ui small hidden divider"></div>
-        <a href="" @click.stop.prevent="showMore = true" v-if="showMore === false">
+        <div class="ui small hidden divider" />
+        <a
+          v-if="showMore === false"
+          href=""
+          @click.stop.prevent="showMore = true"
+        >
           <translate translate-context="*/*/Button,Label">Show more</translate>
         </a>
-        <a href="" @click.stop.prevent="showMore = false" v-else="showMore === true">
+        <a
+          v-else
+          href=""
+          @click.stop.prevent="showMore = false"
+        >
           <translate translate-context="*/*/Button,Label">Show less</translate>
         </a>
-
       </template>
     </template>
     <p v-else-if="!isUpdating">
-      <translate translate-context="*/*/Placeholder">No description available</translate>
+      <translate translate-context="*/*/Placeholder">
+        No description available
+      </translate>
     </p>
     <template v-if="!isUpdating && canUpdate && updateUrl">
-      <div class="ui hidden divider"></div>
-      <span role="button" @click="isUpdating = true">
-        <i class="pencil icon"></i>
+      <div class="ui hidden divider" />
+      <span
+        role="button"
+        @click="isUpdating = true"
+      >
+        <i class="pencil icon" />
         <translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
       </span>
     </template>
-    <form v-if="isUpdating" class="ui form" @submit.prevent="submit()">
-      <div v-if="errors.length > 0" role="alert" class="ui negative message">
-        <h4 class="header"><translate translate-context="Content/Channels/Error message.Title">Error while updating description</translate></h4>
+    <form
+      v-if="isUpdating"
+      class="ui form"
+      @submit.prevent="submit()"
+    >
+      <div
+        v-if="errors.length > 0"
+        role="alert"
+        class="ui negative message"
+      >
+        <h4 class="header">
+          <translate translate-context="Content/Channels/Error message.Title">
+            Error while updating description
+          </translate>
+        </h4>
         <ul class="list">
-          <li v-for="error in errors">{{ error }}</li>
+          <li
+            v-for="(error, key) in errors"
+            :key="key"
+          >
+            {{ error }}
+          </li>
         </ul>
       </div>
-      <content-form v-model="newText" :autofocus="true"></content-form>
-      <a @click.prevent="isUpdating = false" class="left floated">
+      <content-form
+        v-model="newText"
+        :autofocus="true"
+      />
+      <a
+        class="left floated"
+        @click.prevent="isUpdating = false"
+      >
         <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
       </a>
-      <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']" type="submit" :disabled="isLoading">
-        <translate translate-context="Content/Channels/Button.Label/Verb">Update description</translate>
+      <button
+        :class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
+        type="submit"
+        :disabled="isLoading"
+      >
+        <translate translate-context="Content/Channels/Button.Label/Verb">
+          Update description
+        </translate>
       </button>
-      <div class="ui clearing hidden divider"></div>
+      <div class="ui clearing hidden divider" />
     </form>
   </div>
 </template>
 
 <script>
-import {secondsToObject} from '@/filters'
 import axios from 'axios'
 import clip from 'text-clipper'
 
 export default {
   props: {
-    content: {required: true},
-    fieldName: {required: false, default: 'description'},
-    updateUrl: {required: false, type: String},
-    canUpdate: {required: false, default: true, type: Boolean},
-    fetchHtml: {required: false, default: false, type: Boolean},
-    permissive: {required: false, default: false, type: Boolean},
-    truncateLength: {required: false, default: 500, type: Number},
+    content: { type: String, required: true },
+    fieldName: { type: String, required: false, default: 'description' },
+    updateUrl: { required: false, type: String, default: '' },
+    canUpdate: { required: false, default: true, type: Boolean },
+    fetchHtml: { required: false, default: false, type: Boolean },
+    permissive: { required: false, default: false, type: Boolean },
+    truncateLength: { required: false, default: 500, type: Number }
 
   },
   data () {
     return {
       isUpdating: false,
       showMore: false,
-      newText: (this.content || {text: ''}).text,
-      errors: null,
+      newText: (this.content || { text: '' }).text,
       isLoading: false,
       errors: [],
       preview: null
     }
   },
-  async created () {
-    if (this.fetchHtml) {
-      await this.fetchPreview()
-    }
-  },
   computed: {
     html () {
       if (this.fetchHtml) {
@@ -91,21 +125,26 @@ export default {
       return this.truncateLength > 0 && this.truncatedHtml.length < this.content.html.length
     }
   },
+  async created () {
+    if (this.fetchHtml) {
+      await this.fetchPreview()
+    }
+  },
   methods: {
     async fetchPreview () {
-      let response = await axios.post('text-preview/', {text: this.content.text, permissive: this.permissive})
+      const response = await axios.post('text-preview/', { text: this.content.text, permissive: this.permissive })
       this.preview = response.data.rendered
     },
     submit () {
-      let self = this
+      const self = this
       this.isLoading = true
       this.errors = []
-      let payload = {}
+      const payload = {}
       payload[this.fieldName] = null
       if (this.newText) {
         payload[this.fieldName] = {
-          content_type: "text/markdown",
-          text: this.newText,
+          content_type: 'text/markdown',
+          text: this.newText
         }
       }
       axios.patch(this.updateUrl, payload).then((response) => {
@@ -116,7 +155,7 @@ export default {
         self.errors = error.backendErrors
         self.isLoading = false
       })
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/common/Tooltip.vue b/front/src/components/common/Tooltip.vue
index d9ba4c13c..d59d5ef66 100644
--- a/front/src/components/common/Tooltip.vue
+++ b/front/src/components/common/Tooltip.vue
@@ -1,12 +1,15 @@
 <template>
-  <span  class="tooltip" :data-tooltip="content"><i class="question circle icon"></i></span>
+  <span
+    class="tooltip"
+    :data-tooltip="content"
+  ><i class="question circle icon" /></span>
 </template>
 
 <script>
 
 export default {
   props: {
-    content: {type: String, required: true},
+    content: { type: String, required: true }
   }
 }
 </script>
diff --git a/front/src/components/common/UserLink.vue b/front/src/components/common/UserLink.vue
index 6a372e20b..63831c56a 100644
--- a/front/src/components/common/UserLink.vue
+++ b/front/src/components/common/UserLink.vue
@@ -2,11 +2,16 @@
   <span class="component-user-link">
     <template v-if="avatar">
       <img
+        v-if="user.avatar && user.avatar.urls.medium_square_crop"
+        v-lazy="$store.getters['instance/absoluteUrl'](user.avatar.urls.medium_square_crop)"
         class="ui tiny circular avatar"
         alt=""
-        v-if="user.avatar && user.avatar.urls.medium_square_crop"
-        v-lazy="$store.getters['instance/absoluteUrl'](user.avatar.urls.medium_square_crop)" />
-      <span v-else :style="defaultAvatarStyle" class="ui circular label">{{ user.username[0]}}</span>
+      >
+      <span
+        v-else
+        :style="defaultAvatarStyle"
+        class="ui circular label"
+      >{{ user.username[0] }}</span>
       &nbsp;
     </template>
     @{{ user.username }}
@@ -14,12 +19,12 @@
 </template>
 
 <script>
-import {hashCode, intToRGB} from '@/utils/color'
+import { hashCode, intToRGB } from '@/utils/color'
 
 export default {
   props: {
-    user: {required: true},
-    avatar: {type: Boolean, default: true}
+    user: { type: String, required: true },
+    avatar: { type: Boolean, default: true }
   },
   computed: {
     userColor () {
diff --git a/front/src/components/common/UserModal.vue b/front/src/components/common/UserModal.vue
index 928b12d9f..f43e79be1 100644
--- a/front/src/components/common/UserModal.vue
+++ b/front/src/components/common/UserModal.vue
@@ -101,12 +101,12 @@
         </template>
         <div class="row">
           <a
-             class="column"
-             href="https://funkwhale.audio/help"
-             target="_blank"
-             >
-             <i class="user-modal list-icon life ring outline icon" />
-               <span class="user-modal list-item">{{ labels.help }}</span>
+            class="column"
+            href="https://funkwhale.audio/help"
+            target="_blank"
+          >
+            <i class="user-modal list-icon life ring outline icon" />
+            <span class="user-modal list-item">{{ labels.help }}</span>
           </a>
         </div>
         <div class="row">
diff --git a/front/src/components/common/Username.vue b/front/src/components/common/Username.vue
index 17fb34925..7c25a124f 100644
--- a/front/src/components/common/Username.vue
+++ b/front/src/components/common/Username.vue
@@ -3,6 +3,6 @@
 </template>
 <script>
 export default {
-  props: ['username']
+  props: { username: { type: String, required: true } }
 }
 </script>
diff --git a/front/src/components/favorites/List.vue b/front/src/components/favorites/List.vue
index 5618107ba..5b143c4f0 100644
--- a/front/src/components/favorites/List.vue
+++ b/front/src/components/favorites/List.vue
@@ -1,98 +1,158 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
     <section class="ui vertical center aligned stripe segment">
       <div :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']">
         <div class="ui text loader">
-          <translate translate-context="Content/Favorites/Message">Loading your favorites…</translate>
+          <translate translate-context="Content/Favorites/Message">
+            Loading your favorites…
+          </translate>
         </div>
       </div>
-      <h2 v-if="results" class="ui center aligned icon header">
-        <i class="circular inverted heart pink icon"></i>
+      <h2
+        v-if="results"
+        class="ui center aligned icon header"
+      >
+        <i class="circular inverted heart pink icon" />
         <translate
           translate-plural="%{ count } favorites"
           :translate-n="$store.state.favorites.count"
           :translate-params="{count: results.count}"
-          translate-context="Content/Favorites/Title">
-	%{ count } favorite
+          translate-context="Content/Favorites/Title"
+        >
+          %{ count } favorite
         </translate>
       </h2>
-      <radio-button v-if="hasFavorites" type="favorites"></radio-button>
+      <radio-button
+        v-if="hasFavorites"
+        type="favorites"
+      />
     </section>
-    <section v-if="hasFavorites" class="ui vertical stripe segment">
+    <section
+      v-if="hasFavorites"
+      class="ui vertical stripe segment"
+    >
       <div :class="['ui', {'loading': isLoading}, 'form']">
         <div class="fields">
           <div class="field">
             <label for="favorites-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-            <select id="favorites-ordering" class="ui dropdown" v-model="ordering">
-              <option v-for="option in orderingOptions" :value="option[0]" :key="option[0]">
+            <select
+              id="favorites-ordering"
+              v-model="ordering"
+              class="ui dropdown"
+            >
+              <option
+                v-for="option in orderingOptions"
+                :key="option[0]"
+                :value="option[0]"
+              >
                 {{ sharedLabels.filters[option[1]] }}
               </option>
             </select>
           </div>
           <div class="field">
             <label for="favorites-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label>
-            <select id="favorites-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-              <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-              <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+            <select
+              id="favorites-ordering-direction"
+              v-model="orderingDirection"
+              class="ui dropdown"
+            >
+              <option value="+">
+                <translate translate-context="Content/Search/Dropdown">
+                  Ascending
+                </translate>
+              </option>
+              <option value="-">
+                <translate translate-context="Content/Search/Dropdown">
+                  Descending
+                </translate>
+              </option>
             </select>
           </div>
           <div class="field">
             <label for="favorites-results"><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label>
-            <select id="favorites-results" class="ui dropdown" v-model="paginateBy">
-              <option :value="parseInt(12)">12</option>
-              <option :value="parseInt(25)">25</option>
-              <option :value="parseInt(50)">50</option>
+            <select
+              id="favorites-results"
+              v-model="paginateBy"
+              class="ui dropdown"
+            >
+              <option :value="parseInt(12)">
+                12
+              </option>
+              <option :value="parseInt(25)">
+                25
+              </option>
+              <option :value="parseInt(50)">
+                50
+              </option>
             </select>
           </div>
         </div>
       </div>
-      <track-table :show-artist="true" :show-album="true" v-if="results" :tracks="results.results"></track-table>
+      <track-table
+        v-if="results"
+        :show-artist="true"
+        :show-album="true"
+        :tracks="results.results"
+      />
       <div class="ui center aligned basic segment">
         <pagination
           v-if="results && results.count > paginateBy"
-          @page-changed="selectPage"
           :current="page"
           :paginate-by="paginateBy"
           :total="results.count"
-          ></pagination>
+          @page-changed="selectPage"
+        />
       </div>
     </section>
-    <div v-else class="ui placeholder segment">
+    <div
+      v-else
+      class="ui placeholder segment"
+    >
       <div class="ui icon header">
-        <i class="broken heart icon"></i>
+        <i class="broken heart icon" />
         <translate
           translate-context="Content/Home/Placeholder"
-        >No tracks have been added to your favorites yet</translate>
+        >
+          No tracks have been added to your favorites yet
+        </translate>
       </div>
-      <router-link :to="'/library'" class="ui success labeled icon button">
-      <i class="headphones icon"></i>
-        <translate translate-context="Content/*/Verb">Browse the library</translate>
+      <router-link
+        :to="'/library'"
+        class="ui success labeled icon button"
+      >
+        <i class="headphones icon" />
+        <translate translate-context="Content/*/Verb">
+          Browse the library
+        </translate>
       </router-link>
     </div>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import $ from "jquery"
-import logger from "@/logging"
-import RadioButton from "@/components/radios/Button"
-import Pagination from "@/components/Pagination"
-import OrderingMixin from "@/components/mixins/Ordering"
-import PaginationMixin from "@/components/mixins/Pagination"
-import TranslationsMixin from "@/components/mixins/Translations"
-import {checkRedirectToLogin} from '@/utils'
+import axios from 'axios'
+import $ from 'jquery'
+import logger from '@/logging'
+import RadioButton from '@/components/radios/Button'
+import Pagination from '@/components/Pagination'
+import OrderingMixin from '@/components/mixins/Ordering'
+import PaginationMixin from '@/components/mixins/Pagination'
+import TranslationsMixin from '@/components/mixins/Translations'
+import { checkRedirectToLogin } from '@/utils'
 import TrackTable from '@/components/audio/track/Table'
-const FAVORITES_URL = "tracks/"
+const FAVORITES_URL = 'tracks/'
 
 export default {
-  mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
   components: {
     RadioButton,
     Pagination,
     TrackTable
   },
-  data() {
+  mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
+  data () {
     return {
       results: null,
       isLoading: false,
@@ -100,33 +160,46 @@ export default {
       previousLink: null,
       page: parseInt(this.defaultPage),
       orderingOptions: [
-        ["creation_date", "creation_date"],
-        ["title", "track_title"],
-        ["album__title", "album_title"],
-        ["artist__name", "artist_name"]
+        ['creation_date', 'creation_date'],
+        ['title', 'track_title'],
+        ['album__title', 'album_title'],
+        ['artist__name', 'artist_name']
       ]
     }
   },
-  created() {
-    checkRedirectToLogin(this.$store, this.$router)
-    this.fetchFavorites(FAVORITES_URL)
-
-  },
-  mounted() {
-    $(".ui.dropdown").dropdown()
-  },
   computed: {
-    labels() {
+    labels () {
       return {
         title: this.$pgettext('Head/Favorites/Title', 'Your Favorites')
       }
     },
     hasFavorites () {
       return this.$store.state.favorites.count > 0
+    }
+  },
+  watch: {
+    page: function () {
+      this.updateQueryString()
     },
+    paginateBy: function () {
+      this.updateQueryString()
+    },
+    orderingDirection: function () {
+      this.updateQueryString()
+    },
+    ordering: function () {
+      this.updateQueryString()
+    }
+  },
+  created () {
+    checkRedirectToLogin(this.$store, this.$router)
+    this.fetchFavorites(FAVORITES_URL)
+  },
+  mounted () {
+    $('.ui.dropdown').dropdown()
   },
   methods: {
-    updateQueryString: function() {
+    updateQueryString: function () {
       this.$router.replace({
         query: {
           page: this.page,
@@ -136,44 +209,30 @@ export default {
       })
       this.fetchFavorites(FAVORITES_URL)
     },
-    fetchFavorites(url) {
-      var self = this
+    fetchFavorites (url) {
+      const self = this
       this.isLoading = true
-      let params = {
-        favorites: "true",
+      const params = {
+        favorites: 'true',
         page: this.page,
         page_size: this.paginateBy,
         ordering: this.getOrderingAsString()
       }
-      logger.default.time("Loading user favorites")
+      logger.default.time('Loading user favorites')
       axios.get(url, { params: params }).then(response => {
         self.results = response.data
         self.nextLink = response.data.next
         self.previousLink = response.data.previous
         self.results.results.forEach(track => {
-          self.$store.commit("favorites/track", { id: track.id, value: true })
+          self.$store.commit('favorites/track', { id: track.id, value: true })
         })
-        logger.default.timeEnd("Loading user favorites")
+        logger.default.timeEnd('Loading user favorites')
         self.isLoading = false
       })
     },
-    selectPage: function(page) {
+    selectPage: function (page) {
       this.page = page
     }
-  },
-  watch: {
-    page: function() {
-      this.updateQueryString()
-    },
-    paginateBy: function() {
-      this.updateQueryString()
-    },
-    orderingDirection: function() {
-      this.updateQueryString()
-    },
-    ordering: function() {
-      this.updateQueryString()
-    }
   }
 }
 </script>
diff --git a/front/src/components/favorites/TrackFavoriteIcon.vue b/front/src/components/favorites/TrackFavoriteIcon.vue
index de292ca87..80db865f5 100644
--- a/front/src/components/favorites/TrackFavoriteIcon.vue
+++ b/front/src/components/favorites/TrackFavoriteIcon.vue
@@ -1,25 +1,40 @@
- <template>
-  <button @click.stop="$store.dispatch('favorites/toggle', track.id)" v-if="button" :class="['ui', 'pink', {'inverted': isFavorite}, {'favorited': isFavorite}, 'icon', 'labeled', 'button']">
-    <i class="heart icon"></i>
-    <translate v-if="isFavorite" translate-context="Content/Track/Button.Message">In favorites</translate>
-    <translate v-else translate-context="Content/Track/*/Verb">Add to favorites</translate>
+<template>
+  <button
+    v-if="button"
+    :class="['ui', 'pink', {'inverted': isFavorite}, {'favorited': isFavorite}, 'icon', 'labeled', 'button']"
+    @click.stop="$store.dispatch('favorites/toggle', track.id)"
+  >
+    <i class="heart icon" />
+    <translate
+      v-if="isFavorite"
+      translate-context="Content/Track/Button.Message"
+    >
+      In favorites
+    </translate>
+    <translate
+      v-else
+      translate-context="Content/Track/*/Verb"
+    >
+      Add to favorites
+    </translate>
   </button>
   <button
     v-else
-    @click.stop="$store.dispatch('favorites/toggle', track.id)"
     :class="['ui', 'favorite-icon', {'pink': isFavorite}, {'favorited': isFavorite}, 'basic', 'circular', 'icon', {'really': !border}, 'button']"
     :aria-label="title"
-    :title="title">
-    <i :class="['heart', {'pink': isFavorite}, 'basic', 'icon']"></i>
+    :title="title"
+    @click.stop="$store.dispatch('favorites/toggle', track.id)"
+  >
+    <i :class="['heart', {'pink': isFavorite}, 'basic', 'icon']" />
   </button>
 </template>
 
 <script>
 export default {
   props: {
-    track: {type: Object},
-    button: {type: Boolean, default: false},
-    border: {type: Boolean, default: false},
+    track: { type: Object, default: () => { return {} } },
+    button: { type: Boolean, default: false },
+    border: { type: Boolean, default: false }
   },
   computed: {
     title () {
diff --git a/front/src/components/federation/FetchButton.vue b/front/src/components/federation/FetchButton.vue
index 65ff28a09..699def21b 100644
--- a/front/src/components/federation/FetchButton.vue
+++ b/front/src/components/federation/FetchButton.vue
@@ -1,30 +1,73 @@
 <template>
-  <div @click="createFetch" role="button">
+  <div
+    role="button"
+    @click="createFetch"
+  >
     <div>
-      <slot></slot>
+      <slot />
     </div>
-    <modal class="small" :show.sync="showModal">
+    <modal
+      class="small"
+      :show.sync="showModal"
+    >
       <h3 class="header">
-        <translate translate-context="Popup/*/Title">Refreshing object from remote server…</translate>
+        <translate translate-context="Popup/*/Title">
+          Refreshing object from remote server…
+        </translate>
       </h3>
       <div class="scrolling content">
         <template v-if="fetch && fetch.status != 'pending'">
-          <div v-if="fetch.status === 'skipped'" class="ui message">
-            <h4 class="header"><translate translate-context="Popup/*/Message.Title">Refresh was skipped</translate></h4>
-            <p><translate translate-context="Popup/*/Message.Content">The remote server answered, but returned data was unsupported by Funkwhale.</translate></p>
+          <div
+            v-if="fetch.status === 'skipped'"
+            class="ui message"
+          >
+            <h4 class="header">
+              <translate translate-context="Popup/*/Message.Title">
+                Refresh was skipped
+              </translate>
+            </h4>
+            <p>
+              <translate translate-context="Popup/*/Message.Content">
+                The remote server answered, but returned data was unsupported by Funkwhale.
+              </translate>
+            </p>
           </div>
-          <div v-else-if="fetch.status === 'finished'" class="ui success message">
-            <h4 class="header"><translate translate-context="Popup/*/Message.Title">Refresh successful</translate></h4>
-            <p><translate translate-context="Popup/*/Message.Content">Data was refreshed successfully from remote server.</translate></p>
+          <div
+            v-else-if="fetch.status === 'finished'"
+            class="ui success message"
+          >
+            <h4 class="header">
+              <translate translate-context="Popup/*/Message.Title">
+                Refresh successful
+              </translate>
+            </h4>
+            <p>
+              <translate translate-context="Popup/*/Message.Content">
+                Data was refreshed successfully from remote server.
+              </translate>
+            </p>
           </div>
-          <div v-else-if="fetch.status === 'errored'" class="ui error message">
-            <h4 class="header"><translate translate-context="Popup/*/Message.Title">Refresh error</translate></h4>
-            <p><translate translate-context="Popup/*/Message.Content">An error occurred while trying to refresh data:</translate></p>
+          <div
+            v-else-if="fetch.status === 'errored'"
+            class="ui error message"
+          >
+            <h4 class="header">
+              <translate translate-context="Popup/*/Message.Title">
+                Refresh error
+              </translate>
+            </h4>
+            <p>
+              <translate translate-context="Popup/*/Message.Content">
+                An error occurred while trying to refresh data:
+              </translate>
+            </p>
             <table class="ui very basic collapsing celled table">
               <tbody>
                 <tr>
                   <td>
-                    <translate translate-context="Popup/Import/Table.Label/Noun">Error type</translate>
+                    <translate translate-context="Popup/Import/Table.Label/Noun">
+                      Error type
+                    </translate>
                   </td>
                   <td>
                     {{ fetch.detail.error_code }}
@@ -32,61 +75,136 @@
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Popup/Import/Table.Label/Noun">Error detail</translate>
+                    <translate translate-context="Popup/Import/Table.Label/Noun">
+                      Error detail
+                    </translate>
                   </td>
                   <td>
                     <translate
                       v-if="fetch.detail.error_code === 'http' && fetch.detail.status_code"
                       :translate-params="{status: fetch.detail.status_code}"
-                      translate-context="*/*/Error">The remote server answered with HTTP %{ status }</translate>
+                      translate-context="*/*/Error"
+                    >
+                      The remote server answered with HTTP %{ status }
+                    </translate>
                     <translate
                       v-else-if="['http', 'request'].indexOf(fetch.detail.error_code) > -1"
-                      translate-context="*/*/Error">An HTTP error occurred while contacting the remote server</translate>
+                      translate-context="*/*/Error"
+                    >
+                      An HTTP error occurred while contacting the remote server
+                    </translate>
                     <translate
                       v-else-if="fetch.detail.error_code === 'timeout'"
-                      translate-context="*/*/Error">The remote server didn't respond quickly enough</translate>
+                      translate-context="*/*/Error"
+                    >
+                      The remote server didn't respond quickly enough
+                    </translate>
                     <translate
                       v-else-if="fetch.detail.error_code === 'connection'"
-                      translate-context="*/*/Error">Impossible to connect to the remote server</translate>
+                      translate-context="*/*/Error"
+                    >
+                      Impossible to connect to the remote server
+                    </translate>
                     <translate
                       v-else-if="['invalid_json', 'invalid_jsonld', 'missing_jsonld_type'].indexOf(fetch.detail.error_code) > -1"
-                      translate-context="*/*/Error">The remote server returned invalid JSON or JSON-LD data</translate>
-                    <translate v-else-if="fetch.detail.error_code === 'validation'" translate-context="*/*/Error">Data returned by the remote server had invalid or missing attributes</translate>
-                    <translate v-else-if="fetch.detail.error_code === 'unhandled'" translate-context="*/*/Error">Unknown error</translate>
-                    <translate v-else translate-context="*/*/Error">Unknown error</translate>
+                      translate-context="*/*/Error"
+                    >
+                      The remote server returned invalid JSON or JSON-LD data
+                    </translate>
+                    <translate
+                      v-else-if="fetch.detail.error_code === 'validation'"
+                      translate-context="*/*/Error"
+                    >
+                      Data returned by the remote server had invalid or missing attributes
+                    </translate>
+                    <translate
+                      v-else-if="fetch.detail.error_code === 'unhandled'"
+                      translate-context="*/*/Error"
+                    >
+                      Unknown error
+                    </translate>
+                    <translate
+                      v-else
+                      translate-context="*/*/Error"
+                    >
+                      Unknown error
+                    </translate>
                   </td>
                 </tr>
               </tbody>
             </table>
           </div>
         </template>
-        <div v-else-if="isCreatingFetch" class="ui active inverted dimmer">
+        <div
+          v-else-if="isCreatingFetch"
+          class="ui active inverted dimmer"
+        >
           <div class="ui text loader">
-            <translate translate-context="Popup/*/Loading.Title">Requesting a fetch…</translate>
+            <translate translate-context="Popup/*/Loading.Title">
+              Requesting a fetch…
+            </translate>
           </div>
         </div>
-        <div v-else-if="isWaitingFetch" class="ui active inverted dimmer">
+        <div
+          v-else-if="isWaitingFetch"
+          class="ui active inverted dimmer"
+        >
           <div class="ui text loader">
-            <translate translate-context="Popup/*/Loading.Title">Waiting for result…</translate>
+            <translate translate-context="Popup/*/Loading.Title">
+              Waiting for result…
+            </translate>
           </div>
         </div>
-        <div v-if="errors.length > 0" role="alert" class="ui negative message">
-          <h4 class="header"><translate translate-context="Content/*/Error message.Title">Error while saving settings</translate></h4>
+        <div
+          v-if="errors.length > 0"
+          role="alert"
+          class="ui negative message"
+        >
+          <h4 class="header">
+            <translate translate-context="Content/*/Error message.Title">
+              Error while saving settings
+            </translate>
+          </h4>
           <ul class="list">
-            <li v-for="error in errors">{{ error }}</li>
+            <li
+              v-for="(error, key) in errors"
+              :key="key"
+            >
+              {{ error }}
+            </li>
           </ul>
         </div>
-        <div v-else-if="fetch && fetch.status === 'pending' && pollsCount >= maxPolls" role="alert" class="ui warning message">
-          <h4 class="header"><translate translate-context="Popup/*/Message.Title">Refresh pending</translate></h4>
-          <p><translate translate-context="Popup/*/Message.Content">The refresh request hasn't been processed in time by our server. It will be processed later.</translate></p>
+        <div
+          v-else-if="fetch && fetch.status === 'pending' && pollsCount >= maxPolls"
+          role="alert"
+          class="ui warning message"
+        >
+          <h4 class="header">
+            <translate translate-context="Popup/*/Message.Title">
+              Refresh pending
+            </translate>
+          </h4>
+          <p>
+            <translate translate-context="Popup/*/Message.Content">
+              The refresh request hasn't been processed in time by our server. It will be processed later.
+            </translate>
+          </p>
         </div>
       </div>
       <div class="actions">
         <button class="ui basic cancel button">
-          <translate translate-context="*/*/Button.Label/Verb">Close</translate>
+          <translate translate-context="*/*/Button.Label/Verb">
+            Close
+          </translate>
         </button>
-        <button @click.prevent="showModal = false; $emit('refresh')" class="ui confirm success button" v-if="fetch && fetch.status === 'finished'">
-          <translate translate-context="*/*/Button.Label/Verb">Close and reload page</translate>
+        <button
+          v-if="fetch && fetch.status === 'finished'"
+          class="ui confirm success button"
+          @click.prevent="showModal = false; $emit('refresh')"
+        >
+          <translate translate-context="*/*/Button.Label/Verb">
+            Close and reload page
+          </translate>
         </button>
       </div>
     </modal>
@@ -94,14 +212,14 @@
 </template>
 
 <script>
-import axios from "axios"
+import axios from 'axios'
 import Modal from '@/components/semantic/Modal'
 
 export default {
-  props: ['url'],
   components: {
     Modal
   },
+  props: { url: { type: String, required: true } },
   data () {
     return {
       fetch: null,
@@ -110,12 +228,12 @@ export default {
       showModal: false,
       isWaitingFetch: false,
       maxPolls: 15,
-      pollsCount: 0,
+      pollsCount: 0
     }
   },
   methods: {
     createFetch () {
-      let self = this
+      const self = this
       this.fetch = null
       this.pollsCount = 0
       this.errors = []
@@ -134,8 +252,8 @@ export default {
     pollFetch () {
       this.isWaitingFetch = true
       this.pollsCount += 1
-      let url = `federation/fetches/${this.fetch.id}/`
-      let self = this
+      const url = `federation/fetches/${this.fetch.id}/`
+      const self = this
       self.showModal = true
       axios.get(url).then((response) => {
         self.isCreatingFetch = false
diff --git a/front/src/components/federation/LibraryWidget.vue b/front/src/components/federation/LibraryWidget.vue
index 3ee0c9340..9ba011015 100644
--- a/front/src/components/federation/LibraryWidget.vue
+++ b/front/src/components/federation/LibraryWidget.vue
@@ -1,27 +1,52 @@
 <template>
   <div class="wrapper">
-    <h3 v-if="!!this.$slots.title" class="ui header">
-      <slot name="title"></slot>
+    <h3
+      v-if="!!$slots.title"
+      class="ui header"
+    >
+      <slot name="title" />
     </h3>
-    <p v-if="!isLoading && libraries.length > 0" class="ui subtitle"><slot name="subtitle"></slot></p>
-    <p v-if="!isLoading && libraries.length === 0" class="ui subtitle"><translate translate-context="Content/Federation/Paragraph">No matching library.</translate></p>
-    <div class="ui hidden divider"></div>
+    <p
+      v-if="!isLoading && libraries.length > 0"
+      class="ui subtitle"
+    >
+      <slot name="subtitle" />
+    </p>
+    <p
+      v-if="!isLoading && libraries.length === 0"
+      class="ui subtitle"
+    >
+      <translate translate-context="Content/Federation/Paragraph">
+        No matching library.
+      </translate>
+    </p>
+    <div class="ui hidden divider" />
     <div class="ui cards">
-      <div v-if="isLoading" class="ui inverted active dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui inverted active dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <library-card
+        v-for="library in libraries"
+        :key="library.uuid"
         :display-scan="false"
         :display-follow="$store.state.auth.authenticated && library.actor.full_username != $store.state.auth.fullUsername"
         :library="library"
         :display-copy-fid="true"
-        v-for="library in libraries"
-        :key="library.uuid"></library-card>
+      />
     </div>
     <template v-if="nextPage">
-      <div class="ui hidden divider"></div>
-      <button v-if="nextPage" @click="fetchData(nextPage)" :class="['ui', 'basic', 'button']">
-        <translate translate-context="*/*/Button,Label">Show more</translate>
+      <div class="ui hidden divider" />
+      <button
+        v-if="nextPage"
+        :class="['ui', 'basic', 'button']"
+        @click="fetchData(nextPage)"
+      >
+        <translate translate-context="*/*/Button,Label">
+          Show more
+        </translate>
       </button>
     </template>
   </div>
@@ -33,12 +58,12 @@ import axios from 'axios'
 import LibraryCard from '@/views/content/remote/Card'
 
 export default {
-  props: {
-    url: {type: String, required: true}
-  },
   components: {
     LibraryCard
   },
+  props: {
+    url: { type: String, required: true }
+  },
   data () {
     return {
       libraries: [],
@@ -49,17 +74,22 @@ export default {
       nextPage: null
     }
   },
+  watch: {
+    offset () {
+      this.fetchData()
+    }
+  },
   created () {
     this.fetchData(this.url)
   },
   methods: {
     fetchData (url) {
       this.isLoading = true
-      let self = this
-      let params = _.clone({})
+      const self = this
+      const params = _.clone({})
       params.page_size = this.limit
       params.offset = this.offset
-      axios.get(url, {params: params}).then((response) => {
+      axios.get(url, { params: params }).then((response) => {
         self.previousPage = response.data.previous
         self.nextPage = response.data.next
         self.isLoading = false
@@ -77,11 +107,6 @@ export default {
         this.offset = Math.max(this.offset - this.limit, 0)
       }
     }
-  },
-  watch: {
-    offset () {
-      this.fetchData()
-    }
   }
 }
 </script>
diff --git a/front/src/components/forms/PasswordInput.vue b/front/src/components/forms/PasswordInput.vue
index 4b5dac7ea..5b37bf60f 100644
--- a/front/src/components/forms/PasswordInput.vue
+++ b/front/src/components/forms/PasswordInput.vue
@@ -1,55 +1,60 @@
 <template>
   <div class="ui fluid action input">
     <input
+      :id="fieldId"
       required
       name="password"
       :type="passwordInputType"
-      @input="$emit('input', $event.target.value)"
-      :id="fieldId"
       :value="value"
-    />
+      @input="$emit('input', $event.target.value)"
+    >
     <button
-      @click.prevent="showPassword = !showPassword"
       type="button"
       :title="labels.title"
       class="ui icon button"
+      @click.prevent="showPassword = !showPassword"
     >
-      <i class="eye icon"></i>
+      <i class="eye icon" />
     </button>
     <button
       v-if="copyButton"
-      @click.prevent="copyPassword"
       type="button"
       class="ui icon button"
       :title="labels.copy"
+      @click.prevent="copyPassword"
     >
-      <i class="copy icon"></i>
+      <i class="copy icon" />
     </button>
   </div>
 </template>
 <script>
 export default {
-  props: ["value", "defaultShow", "copyButton", "fieldId"],
-  data() {
+  props: {
+    value: { type: String, required: true },
+    defaultShow: { type: Boolean, default: false },
+    copyButton: { type: Boolean, default: false },
+    fieldId: { type: Number, default: 0 }
+  },
+  data () {
     return {
-      showPassword: this.defaultShow || false,
-    };
+      showPassword: this.defaultShow || false
+    }
   },
   computed: {
     labels () {
       return {
         title: this.$pgettext(
-          "Content/Settings/Button.Tooltip/Verb",
-          "Show/hide password"
+          'Content/Settings/Button.Tooltip/Verb',
+          'Show/hide password'
         ),
-        copy: this.$pgettext("*/*/Button.Label/Short, Verb", "Copy"),
+        copy: this.$pgettext('*/*/Button.Label/Short, Verb', 'Copy')
       }
     },
-    passwordInputType() {
+    passwordInputType () {
       if (this.showPassword) {
-        return "text";
+        return 'text'
       }
-      return "password";
+      return 'password'
     }
   },
   methods: {
diff --git a/front/src/components/globals.js b/front/src/components/globals.js
index e3eac9abb..34f802a15 100644
--- a/front/src/components/globals.js
+++ b/front/src/components/globals.js
@@ -1,23 +1,23 @@
 import Vue from 'vue'
 
-Vue.component('human-date', () => import(/* webpackChunkName: "common" */ "@/components/common/HumanDate"))
-Vue.component('human-duration', () => import(/* webpackChunkName: "common" */ "@/components/common/HumanDuration"))
-Vue.component('username', () => import(/* webpackChunkName: "common" */ "@/components/common/Username"))
-Vue.component('user-link', () => import(/* webpackChunkName: "common" */ "@/components/common/UserLink"))
-Vue.component('actor-link', () => import(/* webpackChunkName: "common" */ "@/components/common/ActorLink"))
-Vue.component('actor-avatar', () => import(/* webpackChunkName: "common" */ "@/components/common/ActorAvatar"))
-Vue.component('duration', () => import(/* webpackChunkName: "common" */ "@/components/common/Duration"))
-Vue.component('dangerous-button', () => import(/* webpackChunkName: "common" */ "@/components/common/DangerousButton"))
-Vue.component('message', () => import(/* webpackChunkName: "common" */ "@/components/common/Message"))
-Vue.component('copy-input', () => import(/* webpackChunkName: "common" */ "@/components/common/CopyInput"))
-Vue.component('ajax-button', () => import(/* webpackChunkName: "common" */ "@/components/common/AjaxButton"))
-Vue.component('tooltip', () => import(/* webpackChunkName: "common" */ "@/components/common/Tooltip"))
-Vue.component('empty-state', () => import(/* webpackChunkName: "common" */ "@/components/common/EmptyState"))
-Vue.component('expandable-div', () => import(/* webpackChunkName: "common" */ "@/components/common/ExpandableDiv"))
-Vue.component('collapse-link', () => import(/* webpackChunkName: "common" */ "@/components/common/CollapseLink"))
-Vue.component('action-feedback', () => import(/* webpackChunkName: "common" */ "@/components/common/ActionFeedback"))
-Vue.component('rendered-description', () => import(/* webpackChunkName: "common" */ "@/components/common/RenderedDescription"))
-Vue.component('content-form', () => import(/* webpackChunkName: "common" */ "@/components/common/ContentForm"))
-Vue.component('inline-search-bar', () => import(/* webpackChunkName: "common" */ "@/components/common/InlineSearchBar"))
+Vue.component('HumanDate', () => import(/* webpackChunkName: "common" */ '@/components/common/HumanDate'))
+Vue.component('HumanDuration', () => import(/* webpackChunkName: "common" */ '@/components/common/HumanDuration'))
+Vue.component('Username', () => import(/* webpackChunkName: "common" */ '@/components/common/Username'))
+Vue.component('UserLink', () => import(/* webpackChunkName: "common" */ '@/components/common/UserLink'))
+Vue.component('ActorLink', () => import(/* webpackChunkName: "common" */ '@/components/common/ActorLink'))
+Vue.component('ActorAvatar', () => import(/* webpackChunkName: "common" */ '@/components/common/ActorAvatar'))
+Vue.component('Duration', () => import(/* webpackChunkName: "common" */ '@/components/common/Duration'))
+Vue.component('DangerousButton', () => import(/* webpackChunkName: "common" */ '@/components/common/DangerousButton'))
+Vue.component('Message', () => import(/* webpackChunkName: "common" */ '@/components/common/Message'))
+Vue.component('CopyInput', () => import(/* webpackChunkName: "common" */ '@/components/common/CopyInput'))
+Vue.component('AjaxButton', () => import(/* webpackChunkName: "common" */ '@/components/common/AjaxButton'))
+Vue.component('Tooltip', () => import(/* webpackChunkName: "common" */ '@/components/common/Tooltip'))
+Vue.component('EmptyState', () => import(/* webpackChunkName: "common" */ '@/components/common/EmptyState'))
+Vue.component('ExpandableDiv', () => import(/* webpackChunkName: "common" */ '@/components/common/ExpandableDiv'))
+Vue.component('CollapseLink', () => import(/* webpackChunkName: "common" */ '@/components/common/CollapseLink'))
+Vue.component('ActionFeedback', () => import(/* webpackChunkName: "common" */ '@/components/common/ActionFeedback'))
+Vue.component('RenderedDescription', () => import(/* webpackChunkName: "common" */ '@/components/common/RenderedDescription'))
+Vue.component('ContentForm', () => import(/* webpackChunkName: "common" */ '@/components/common/ContentForm'))
+Vue.component('InlineSearchBar', () => import(/* webpackChunkName: "common" */ '@/components/common/InlineSearchBar'))
 
 export default {}
diff --git a/front/src/components/library/AlbumBase.vue b/front/src/components/library/AlbumBase.vue
index 87e14deb3..c8bf35349 100644
--- a/front/src/components/library/AlbumBase.vue
+++ b/front/src/components/library/AlbumBase.vue
@@ -1,39 +1,90 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment" v-title="labels.title">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      v-title="labels.title"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object">
       <section class="ui vertical stripe segment channel-serie">
         <div class="ui stackable grid container">
           <div class="ui seven wide column">
-            <div v-if="isSerie" class="padded basic segment">
-              <div class="ui two column grid" v-if="isSerie">
+            <div
+              v-if="isSerie"
+              class="padded basic segment"
+            >
+              <div
+                v-if="isSerie"
+                class="ui two column grid"
+              >
                 <div class="column">
                   <div class="large two-images">
-                    <img alt="" class="channel-image" v-if="object.cover && object.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.medium_square_crop)">
-                    <img alt="" class="channel-image" v-else src="../../assets/audio/default-cover.png">
-                    <img alt="" class="channel-image" v-if="object.cover && object.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.medium_square_crop)">
-                    <img alt="" class="channel-image" v-else src="../../assets/audio/default-cover.png">
+                    <img
+                      v-if="object.cover && object.cover.urls.original"
+                      v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.medium_square_crop)"
+                      alt=""
+                      class="channel-image"
+                    >
+                    <img
+                      v-else
+                      alt=""
+                      class="channel-image"
+                      src="../../assets/audio/default-cover.png"
+                    >
+                    <img
+                      v-if="object.cover && object.cover.urls.original"
+                      v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.medium_square_crop)"
+                      alt=""
+                      class="channel-image"
+                    >
+                    <img
+                      v-else
+                      alt=""
+                      class="channel-image"
+                      src="../../assets/audio/default-cover.png"
+                    >
                   </div>
                 </div>
                 <div class="ui column right aligned">
-                  <tags-list v-if="object.tags && object.tags.length > 0" :tags="object.tags"></tags-list>
-                  <div class="ui small hidden divider"></div>
-                  <human-duration v-if="totalDuration > 0" :duration="totalDuration"></human-duration>
+                  <tags-list
+                    v-if="object.tags && object.tags.length > 0"
+                    :tags="object.tags"
+                  />
+                  <div class="ui small hidden divider" />
+                  <human-duration
+                    v-if="totalDuration > 0"
+                    :duration="totalDuration"
+                  />
                   <template v-if="totalTracks > 0">
-                    <div class="ui hidden very small divider"></div>
-                    <translate key="1" v-if="isSerie" translate-context="Content/Channel/Paragraph"
+                    <div class="ui hidden very small divider" />
+                    <translate
+                      v-if="isSerie"
+                      key="1"
+                      translate-context="Content/Channel/Paragraph"
                       translate-plural="%{ count } episodes"
                       :translate-n="totalTracks"
-                      :translate-params="{count: totalTracks}">
+                      :translate-params="{count: totalTracks}"
+                    >
                       %{ count } episode
                     </translate>
-                    <translate v-else translate-context="*/*/*" :translate-params="{count: totalTracks}" :translate-n="totalTracks" translate-plural="%{ count } tracks">%{ count } track</translate>
+                    <translate
+                      v-else
+                      translate-context="*/*/*"
+                      :translate-params="{count: totalTracks}"
+                      :translate-n="totalTracks"
+                      translate-plural="%{ count } tracks"
+                    >
+                      %{ count } track
+                    </translate>
                   </template>
-                  <div class="ui small hidden divider"></div>
-                  <play-button class="vibrant" :tracks="object.tracks"></play-button>
-                  <div class="ui hidden horizontal divider"></div>
+                  <div class="ui small hidden divider" />
+                  <play-button
+                    class="vibrant"
+                    :tracks="object.tracks"
+                  />
+                  <div class="ui hidden horizontal divider" />
                   <album-dropdown
                     :object="object"
                     :public-libraries="publicLibraries"
@@ -41,42 +92,86 @@
                     :is-album="isAlbum"
                     :is-serie="isSerie"
                     :is-channel="isChannel"
-                    :artist="artist"></album-dropdown>
+                    :artist="artist"
+                  />
                 </div>
               </div>
-              <div class="ui small hidden divider"></div>
+              <div class="ui small hidden divider" />
               <header>
-                <h2 class="ui header" :title="object.title">
+                <h2
+                  class="ui header"
+                  :title="object.title"
+                >
                   {{ object.title }}
                 </h2>
-                <artist-label :artist="artist"></artist-label>
+                <artist-label :artist="artist" />
               </header>
             </div>
-            <div v-else class="ui center aligned text padded basic segment">
-              <img alt="" class="channel-image" v-if="object.cover && object.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.medium_square_crop)">
-              <img alt="" class="channel-image" v-else src="../../assets/audio/default-cover.png">
-              <div class="ui hidden divider"></div>
+            <div
+              v-else
+              class="ui center aligned text padded basic segment"
+            >
+              <img
+                v-if="object.cover && object.cover.urls.original"
+                v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.medium_square_crop)"
+                alt=""
+                class="channel-image"
+              >
+              <img
+                v-else
+                alt=""
+                class="channel-image"
+                src="../../assets/audio/default-cover.png"
+              >
+              <div class="ui hidden divider" />
               <header>
-                <h2 class="ui header" :title="object.title">
+                <h2
+                  class="ui header"
+                  :title="object.title"
+                >
                   {{ object.title }}
                 </h2>
-                <artist-label class="rounded" :artist="artist"></artist-label>
+                <artist-label
+                  class="rounded"
+                  :artist="artist"
+                />
               </header>
-              <div v-if="object.release_date || (totalTracks > 0)" class="ui small hidden divider"></div>
+              <div
+                v-if="object.release_date || (totalTracks > 0)"
+                class="ui small hidden divider"
+              />
               <span v-if="object.release_date">{{ object.release_date | moment('Y') }} · </span>
               <template v-if="totalTracks > 0">
-                <translate key="1" v-if="isSerie" translate-context="Content/Channel/Paragraph"
+                <translate
+                  v-if="isSerie"
+                  key="1"
+                  translate-context="Content/Channel/Paragraph"
                   translate-plural="%{ count } episodes"
                   :translate-n="totalTracks"
-                  :translate-params="{count: totalTracks}">
+                  :translate-params="{count: totalTracks}"
+                >
                   %{ count } episode
                 </translate>
-                <translate v-else translate-context="*/*/*" :translate-params="{count: totalTracks}" :translate-n="totalTracks" translate-plural="%{ count } tracks">%{ count } track</translate> ·
+                <translate
+                  v-else
+                  translate-context="*/*/*"
+                  :translate-params="{count: totalTracks}"
+                  :translate-n="totalTracks"
+                  translate-plural="%{ count } tracks"
+                >
+                  %{ count } track
+                </translate> ·
               </template>
-              <human-duration v-if="totalDuration > 0" :duration="totalDuration"></human-duration>
-              <div class="ui small hidden divider"></div>
-              <play-button class="vibrant" :album="object"></play-button>
-              <div class="ui horizontal hidden divider"></div>
+              <human-duration
+                v-if="totalDuration > 0"
+                :duration="totalDuration"
+              />
+              <div class="ui small hidden divider" />
+              <play-button
+                class="vibrant"
+                :album="object"
+              />
+              <div class="ui horizontal hidden divider" />
               <album-dropdown
                 :object="object"
                 :public-libraries="publicLibraries"
@@ -85,40 +180,64 @@
                 :is-serie="isSerie"
                 :is-channel="isChannel"
                 :artist="artist"
-                ></album-dropdown>
+              />
               <div v-if="(object.tags && object.tags.length > 0) || object.description || $store.state.auth.authenticated && object.is_local">
-                <div class="ui small hidden divider"></div>
-                <div class="ui divider"></div>
-                <div class="ui small hidden divider"></div>
-                <template v-if="object.tags && object.tags.length > 0" >
-                  <tags-list :tags="object.tags"></tags-list>
-                  <div class="ui small hidden divider"></div>
+                <div class="ui small hidden divider" />
+                <div class="ui divider" />
+                <div class="ui small hidden divider" />
+                <template v-if="object.tags && object.tags.length > 0">
+                  <tags-list :tags="object.tags" />
+                  <div class="ui small hidden divider" />
                 </template>
                 <rendered-description
                   v-if="object.description"
                   :content="object.description"
-                  :can-update="false"></rendered-description>
-                <router-link v-else-if="$store.state.auth.authenticated && object.is_local" :to="{name: 'library.albums.edit', params: {id: object.id }}">
-                  <i class="pencil icon"></i>
-                  <translate translate-context="Content/*/Button.Label/Verb">Add a description…</translate>
+                  :can-update="false"
+                />
+                <router-link
+                  v-else-if="$store.state.auth.authenticated && object.is_local"
+                  :to="{name: 'library.albums.edit', params: {id: object.id }}"
+                >
+                  <i class="pencil icon" />
+                  <translate translate-context="Content/*/Button.Label/Verb">
+                    Add a description…
+                  </translate>
                 </router-link>
               </div>
             </div>
             <template v-if="isSerie">
-              <div class="ui hidden divider"></div>
+              <div class="ui hidden divider" />
               <rendered-description
                 v-if="object.description"
                 :content="object.description"
-                :can-update="false"></rendered-description>
-              <router-link v-else-if="$store.state.auth.authenticated && object.is_local" :to="{name: 'library.albums.edit', params: {id: object.id }}">
-                <i class="pencil icon"></i>
-                <translate translate-context="Content/*/Button.Label/Verb">Add a description…</translate>
+                :can-update="false"
+              />
+              <router-link
+                v-else-if="$store.state.auth.authenticated && object.is_local"
+                :to="{name: 'library.albums.edit', params: {id: object.id }}"
+              >
+                <i class="pencil icon" />
+                <translate translate-context="Content/*/Button.Label/Verb">
+                  Add a description…
+                </translate>
               </router-link>
-
             </template>
           </div>
           <div class="nine wide column">
-            <router-view v-if="object" :paginate-by="paginateBy" :page="page" :total-tracks="totalTracks" :is-serie="isSerie" :artist="artist" :discs="discs" @libraries-loaded="libraries = $event" :object="object" object-type="album" :key="$route.fullPath" @page-changed="page = $event"></router-view>
+            <router-view
+              v-if="object"
+              :key="$route.fullPath"
+              :paginate-by="paginateBy"
+              :page="page"
+              :total-tracks="totalTracks"
+              :is-serie="isSerie"
+              :artist="artist"
+              :discs="discs"
+              :object="object"
+              object-type="album"
+              @libraries-loaded="libraries = $event"
+              @page-changed="page = $event"
+            />
           </div>
         </div>
       </section>
@@ -127,17 +246,17 @@
 </template>
 
 <script>
-import axios from "axios"
-import lodash from "@/lodash"
-import PlayButton from "@/components/audio/PlayButton"
-import TagsList from "@/components/tags/List"
+import axios from 'axios'
+import lodash from '@/lodash'
+import PlayButton from '@/components/audio/PlayButton'
+import TagsList from '@/components/tags/List'
 import ArtistLabel from '@/components/audio/ArtistLabel'
 import AlbumDropdown from './AlbumDropdown'
 
-function groupByDisc(initial) {
-  function inner(acc, track) {
-    var dn = track.disc_number - initial
-    if (acc[dn] == undefined) {
+function groupByDisc (initial) {
+  function inner (acc, track) {
+    const dn = track.disc_number - initial
+    if (acc[dn] === undefined) {
       acc.push([track])
     } else {
       acc[dn].push(track)
@@ -148,14 +267,14 @@ function groupByDisc(initial) {
 }
 
 export default {
-  props: ["id"],
   components: {
     PlayButton,
     TagsList,
     ArtistLabel,
     AlbumDropdown
   },
-  data() {
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       isLoading: true,
       object: null,
@@ -163,39 +282,7 @@ export default {
       discs: [],
       libraries: [],
       page: 1,
-      paginateBy: 50,
-    }
-  },
-  async created() {
-    await this.fetchData()
-  },
-  methods: {
-    async fetchData() {
-      this.isLoading = true
-      let tracksResponse = axios.get(`tracks/`, {params: {ordering: 'disc_number,position', album: this.id, page_size: this.paginateBy, page:this.page, include_channels: 'true', playable: 'true'}})
-      let albumResponse = await axios.get(`albums/${this.id}/`, {params: {refresh: 'true'}})
-      let artistResponse = await axios.get(`artists/${albumResponse.data.artist.id}/`)
-      this.artist = artistResponse.data
-      if (this.artist.channel) {
-        this.artist.channel.artist = this.artist
-      }
-      tracksResponse = await tracksResponse
-      this.object = albumResponse.data
-      this.object.tracks = tracksResponse.data.results
-      this.discs = this.object.tracks.reduce(groupByDisc(this.object.tracks[0].disc_number), [])
-      this.isLoading = false
-    },
-    remove () {
-      let self = this
-      self.isLoading = true
-      axios.delete(`albums/${this.object.id}`).then((response) => {
-        self.isLoading = false
-        self.$emit('deleted')
-        self.$router.push({name: 'library.artists.detail', params: {id: this.artist.id}})
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
+      paginateBy: 50
     }
   },
   computed: {
@@ -212,7 +299,7 @@ export default {
       return this.object.artist.content_category === 'music'
     },
     totalDuration () {
-      let durations = [0]
+      const durations = [0]
       this.object.tracks.forEach((t) => {
         if (t.uploads[0] && t.uploads[0].duration) {
           durations.push(t.uploads[0].duration)
@@ -220,24 +307,56 @@ export default {
       })
       return lodash.sum(durations)
     },
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext('*/*/*', 'Album'),
+        title: this.$pgettext('*/*/*', 'Album')
       }
     },
     publicLibraries () {
       return this.libraries.filter(l => {
         return l.privacy_level === 'everyone'
       })
-    },
+    }
   },
   watch: {
-    id() {
+    id () {
       this.fetchData()
     },
-    page() {
+    page () {
       this.fetchData()
     }
+  },
+  async created () {
+    await this.fetchData()
+  },
+  methods: {
+    async fetchData () {
+      this.isLoading = true
+      let tracksResponse = axios.get('tracks/', { params: { ordering: 'disc_number,position', album: this.id, page_size: this.paginateBy, page: this.page, include_channels: 'true', playable: 'true' } })
+      const albumResponse = await axios.get(`albums/${this.id}/`, { params: { refresh: 'true' } })
+      const artistResponse = await axios.get(`artists/${albumResponse.data.artist.id}/`)
+      this.artist = artistResponse.data
+      if (this.artist.channel) {
+        this.artist.channel.artist = this.artist
+      }
+      tracksResponse = await tracksResponse
+      this.object = albumResponse.data
+      this.object.tracks = tracksResponse.data.results
+      this.discs = this.object.tracks.reduce(groupByDisc(this.object.tracks[0].disc_number), [])
+      this.isLoading = false
+    },
+    remove () {
+      const self = this
+      self.isLoading = true
+      axios.delete(`albums/${this.object.id}`).then((response) => {
+        self.isLoading = false
+        self.$emit('deleted')
+        self.$router.push({ name: 'library.artists.detail', params: { id: this.artist.id } })
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    }
   }
 }
 </script>
diff --git a/front/src/components/library/AlbumDetail.vue b/front/src/components/library/AlbumDetail.vue
index a584a372a..635329c16 100644
--- a/front/src/components/library/AlbumDetail.vue
+++ b/front/src/components/library/AlbumDetail.vue
@@ -1,21 +1,45 @@
 <template>
   <div v-if="object">
     <h2 class="ui header">
-      <translate key="1" v-if="isSerie" translate-context="Content/Channels/*">Episodes</translate>
-      <translate key="2" v-else translate-context="*/*/*">Tracks</translate>
+      <translate
+        v-if="isSerie"
+        key="1"
+        translate-context="Content/Channels/*"
+      >
+        Episodes
+      </translate>
+      <translate
+        v-else
+        key="2"
+        translate-context="*/*/*"
+      >
+        Tracks
+      </translate>
     </h2>
-    <channel-entries v-if="artist.channel && isSerie" :is-podcast="isSerie" :limit="50" :filters="{channel: artist.channel.uuid, album: object.id, ordering: '-creation_date'}">
-    </channel-entries>
+    <channel-entries
+      v-if="artist.channel && isSerie"
+      :is-podcast="isSerie"
+      :limit="50"
+      :filters="{channel: artist.channel.uuid, album: object.id, ordering: '-creation_date'}"
+    />
     <template v-else-if="discs && discs.length > 1">
-      <div v-for="tracks in discs" :key="tracks.disc_number">
-        <div class="ui hidden divider"></div>
-        <play-button class="right floated mini inverted vibrant" :tracks="tracks"></play-button>
+      <div
+        v-for="tracks in discs"
+        :key="tracks.disc_number"
+      >
+        <div class="ui hidden divider" />
+        <play-button
+          class="right floated mini inverted vibrant"
+          :tracks="tracks"
+        />
         <translate
           tag="h3"
           :translate-params="{number: tracks[0].disc_number}"
           translate-context="Content/Album/"
-        >Volume %{ number }</translate>
-        <track-table 
+        >
+          Volume %{ number }
+        </translate>
+        <track-table
           :is-album="true"
           :tracks="object.tracks"
           :show-position="true"
@@ -26,12 +50,12 @@
           :total="totalTracks"
           :paginate-by="paginateBy"
           :page="page"
-          @page-changed="updatePage">
-        </track-table>
+          @page-changed="updatePage"
+        />
       </div>
     </template>
     <template v-else>
-      <track-table 
+      <track-table
         :is-album="true"
         :tracks="object.tracks"
         :show-position="true"
@@ -42,15 +66,25 @@
         :total="totalTracks"
         :paginate-by="paginateBy"
         :page="page"
-        @page-changed="updatePage">
-      </track-table>
+        @page-changed="updatePage"
+      />
     </template>
     <template v-if="!artist.channel && !isSerie">
       <h2>
-        <translate translate-context="Content/*/Title/Noun">User libraries</translate>
+        <translate translate-context="Content/*/Title/Noun">
+          User libraries
+        </translate>
       </h2>
-      <library-widget @loaded="$emit('libraries-loaded', $event)" :url="'albums/' + object.id + '/libraries/'">
-        <translate slot="subtitle" translate-context="Content/Album/Paragraph">This album is present in the following libraries:</translate>
+      <library-widget
+        :url="'albums/' + object.id + '/libraries/'"
+        @loaded="$emit('libraries-loaded', $event)"
+      >
+        <translate
+          slot="subtitle"
+          translate-context="Content/Album/Paragraph"
+        >
+          This album is present in the following libraries:
+        </translate>
       </library-widget>
     </template>
   </div>
@@ -58,30 +92,38 @@
 
 <script>
 
-import time from "@/utils/time"
-import LibraryWidget from "@/components/federation/LibraryWidget"
+import time from '@/utils/time'
+import LibraryWidget from '@/components/federation/LibraryWidget'
 import ChannelEntries from '@/components/audio/ChannelEntries'
 import TrackTable from '@/components/audio/track/Table'
-import PlayButton from "@/components/audio/PlayButton"
+import PlayButton from '@/components/audio/PlayButton'
 
 export default {
-  props: ["object", "libraries", "discs", "isSerie", "artist", "page", "paginateBy", "totalTracks"],
   components: {
     LibraryWidget,
     TrackTable,
     ChannelEntries,
     PlayButton
   },
-  data() {
+  props: {
+    object: { type: Object, required: true },
+    discs: { type: Array, required: true },
+    isSerie: { type: Boolean, required: true },
+    artist: { type: Object, required: true },
+    page: { type: Number, required: true },
+    paginateBy: { type: Number, required: true },
+    totalTracks: { type: Number, required: true }
+  },
+  data () {
     return {
       time,
-      id: this.object.id,
+      id: this.object.id
     }
   },
   methods: {
-    updatePage: function(page) {
+    updatePage: function (page) {
       this.$emit('page-changed', page)
     }
-  },
+  }
 }
 </script>
diff --git a/front/src/components/library/AlbumDropdown.vue b/front/src/components/library/AlbumDropdown.vue
index 3cc42f6ef..a7f12e155 100644
--- a/front/src/components/library/AlbumDropdown.vue
+++ b/front/src/components/library/AlbumDropdown.vue
@@ -1,13 +1,19 @@
 <template>
   <span>
 
-    <modal v-if="isEmbedable" :show.sync="showEmbedModal">
+    <modal
+      v-if="isEmbedable"
+      :show.sync="showEmbedModal"
+    >
       <h4 class="header">
         <translate translate-context="Popup/Album/Title/Verb">Embed this album on your website</translate>
       </h4>
       <div class="scrolling content">
         <div class="description">
-          <embed-wizard type="album" :id="object.id" />
+          <embed-wizard
+            :id="object.id"
+            type="album"
+          />
 
         </div>
       </div>
@@ -17,46 +23,69 @@
         </button>
       </div>
     </modal>
-    <button class="ui floating dropdown circular icon basic button" :title="labels.more" v-dropdown="{direction: 'downward'}">
-      <i class="ellipsis vertical icon"></i>
+    <button
+      v-dropdown="{direction: 'downward'}"
+      class="ui floating dropdown circular icon basic button"
+      :title="labels.more"
+    >
+      <i class="ellipsis vertical icon" />
       <div class="menu">
         <a
-          :href="object.fid"
           v-if="domain != $store.getters['instance/domain']"
+          :href="object.fid"
           target="_blank"
-          class="basic item">
-          <i class="external icon"></i>
-          <translate :translate-params="{domain: domain}" translate-context="Content/*/Button.Label/Verb">View on %{ domain }</translate>
+          class="basic item"
+        >
+          <i class="external icon" />
+          <translate
+            :translate-params="{domain: domain}"
+            translate-context="Content/*/Button.Label/Verb"
+          >View on %{ domain }</translate>
         </a>
 
         <div
-          role="button"
           v-if="isEmbedable"
+          role="button"
+          class="basic item"
           @click="showEmbedModal = !showEmbedModal"
-          class="basic item">
-          <i class="code icon"></i>
+        >
+          <i class="code icon" />
           <translate translate-context="Content/*/Button.Label/Verb">Embed</translate>
         </div>
-        <a v-if="isAlbum && musicbrainzUrl" :href="musicbrainzUrl" target="_blank" rel="noreferrer noopener" class="basic item">
-          <i class="external icon"></i>
+        <a
+          v-if="isAlbum && musicbrainzUrl"
+          :href="musicbrainzUrl"
+          target="_blank"
+          rel="noreferrer noopener"
+          class="basic item"
+        >
+          <i class="external icon" />
           <translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate>
         </a>
-        <a v-if="!isChannel && isAlbum" :href="discogsUrl" target="_blank" rel="noreferrer noopener" class="basic item">
-          <i class="external icon"></i>
+        <a
+          v-if="!isChannel && isAlbum"
+          :href="discogsUrl"
+          target="_blank"
+          rel="noreferrer noopener"
+          class="basic item"
+        >
+          <i class="external icon" />
           <translate translate-context="Content/*/Button.Label/Verb">Search on Discogs</translate>
-                    </a>
+        </a>
         <router-link
           v-if="object.is_local"
           :to="{name: 'library.albums.edit', params: {id: object.id }}"
-          class="basic item">
-          <i class="edit icon"></i>
+          class="basic item"
+        >
+          <i class="edit icon" />
           <translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
         </router-link>
         <dangerous-button
-          :class="['ui', {loading: isLoading}, 'item']"
           v-if="artist && $store.state.auth.authenticated && artist.channel && artist.attributed_to.full_username === $store.state.auth.fullUsername"
-          @confirm="remove()">
-          <i class="ui trash icon"></i>
+          :class="['ui', {loading: isLoading}, 'item']"
+          @confirm="remove()"
+        >
+          <i class="ui trash icon" />
           <translate translate-context="*/*/*/Verb">Delete…</translate>
           <p slot="modal-header"><translate translate-context="Popup/Channel/Title">Delete this album?</translate></p>
           <div slot="modal-content">
@@ -64,26 +93,33 @@
           </div>
           <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p>
         </dangerous-button>
-        <div class="divider"></div>
+        <div class="divider" />
         <div
-          role="button"
-          class="basic item"
           v-for="obj in getReportableObjs({album: object, channel: artist.channel})"
           :key="obj.target.type + obj.target.id"
-          @click.stop.prevent="$store.dispatch('moderation/report', obj.target)">
+          role="button"
+          class="basic item"
+          @click.stop.prevent="$store.dispatch('moderation/report', obj.target)"
+        >
           <i class="share icon" /> {{ obj.label }}
         </div>
-        <div class="divider"></div>
-        <router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.albums.detail', params: {id: object.id}}">
-          <i class="wrench icon"></i>
+        <div class="divider" />
+        <router-link
+          v-if="$store.state.auth.availablePermissions['library']"
+          class="basic item"
+          :to="{name: 'manage.library.albums.detail', params: {id: object.id}}"
+        >
+          <i class="wrench icon" />
           <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
         </router-link>
         <a
           v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
           class="basic item"
           :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/album/${object.id}`)"
-          target="_blank" rel="noopener noreferrer">
-          <i class="wrench icon"></i>
+          target="_blank"
+          rel="noopener noreferrer"
+        >
+          <i class="wrench icon" />
           <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
         </a>
       </div>
@@ -91,30 +127,30 @@
   </span>
 </template>
 <script>
-import EmbedWizard from "@/components/audio/EmbedWizard"
+import EmbedWizard from '@/components/audio/EmbedWizard'
 import Modal from '@/components/semantic/Modal'
 import ReportMixin from '@/components/mixins/Report'
 
-import {getDomain} from '@/utils'
+import { getDomain } from '@/utils'
 
 export default {
+  components: {
+    EmbedWizard,
+    Modal
+  },
   mixins: [ReportMixin],
   props: {
     isLoading: Boolean,
-    artist: Object,
-    object: Object,
-    publicLibraries: Array,
+    artist: { type: Object, required: true },
+    object: { type: Object, required: true },
+    publicLibraries: { type: Array, required: true },
     isAlbum: Boolean,
     isChannel: Boolean,
-    isSerie: Boolean,
-  },
-  components: {
-    EmbedWizard,
-    Modal,
+    isSerie: Boolean
   },
   data () {
     return {
-      showEmbedModal: false,
+      showEmbedModal: false
     }
   },
   computed: {
@@ -122,28 +158,30 @@ export default {
       if (this.object) {
         return getDomain(this.object.fid)
       }
+      return null
     },
-    labels() {
+    labels () {
       return {
-        more: this.$pgettext('*/*/Button.Label/Noun', "More…"),
+        more: this.$pgettext('*/*/Button.Label/Noun', 'More…')
       }
     },
     isEmbedable () {
       return (this.isChannel && this.artist.channel.actor) || this.publicLibraries.length > 0
     },
 
-    musicbrainzUrl() {
+    musicbrainzUrl () {
       if (this.object.mbid) {
-        return "https://musicbrainz.org/release/" + this.object.mbid
+        return 'https://musicbrainz.org/release/' + this.object.mbid
       }
+      return null
     },
-    discogsUrl() {
+    discogsUrl () {
       return (
-        "https://discogs.com/search/?type=release&title=" +
-        encodeURI(this.object.title) + "&artist=" +
+        'https://discogs.com/search/?type=release&title=' +
+        encodeURI(this.object.title) + '&artist=' +
         encodeURI(this.object.artist.name)
       )
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/library/AlbumEdit.vue b/front/src/components/library/AlbumEdit.vue
index b7c24737c..d4d9559c3 100644
--- a/front/src/components/library/AlbumEdit.vue
+++ b/front/src/components/library/AlbumEdit.vue
@@ -1,37 +1,56 @@
 <template>
-
   <section class="ui vertical stripe segment">
     <div class="ui text container">
       <h2>
-        <translate v-if="canEdit" key="1" translate-context="Content/*/Title">Edit this album</translate>
-        <translate v-else key="2" translate-context="Content/*/Title">Suggest an edit on this album</translate>
+        <translate
+          v-if="canEdit"
+          key="1"
+          translate-context="Content/*/Title"
+        >
+          Edit this album
+        </translate>
+        <translate
+          v-else
+          key="2"
+          translate-context="Content/*/Title"
+        >
+          Suggest an edit on this album
+        </translate>
       </h2>
-      <div class="ui message" v-if="!object.is_local">
-        <translate translate-context="Content/*/Message">This object is managed by another server, you cannot edit it.</translate>
+      <div
+        v-if="!object.is_local"
+        class="ui message"
+      >
+        <translate translate-context="Content/*/Message">
+          This object is managed by another server, you cannot edit it.
+        </translate>
       </div>
       <edit-form
         v-else
         :object-type="objectType"
         :object="object"
-        :can-edit="canEdit"></edit-form>
+        :can-edit="canEdit"
+      />
     </div>
   </section>
 </template>
 
 <script>
-import axios from "axios"
-
 import EditForm from '@/components/library/EditForm'
 export default {
-  props: ["objectType", "object", "libraries"],
-  data() {
-    return {
-      id: this.object.id,
-    }
-  },
   components: {
     EditForm
   },
+  props: {
+    objectType: { type: String, required: true },
+    object: { type: Object, required: true },
+    libraries: { type: Array, required: true }
+  },
+  data () {
+    return {
+      id: this.object.id
+    }
+  },
   computed: {
     canEdit () {
       return true
diff --git a/front/src/components/library/Albums.vue b/front/src/components/library/Albums.vue
index 97589d80e..14e499d9a 100644
--- a/front/src/components/library/Albums.vue
+++ b/front/src/components/library/Albums.vue
@@ -2,78 +2,131 @@
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
       <h2 class="ui header">
-        <translate translate-context="Content/Album/Title">Browsing albums</translate>
+        <translate translate-context="Content/Album/Title">
+          Browsing albums
+        </translate>
       </h2>
-      <form :class="['ui', {'loading': isLoading}, 'form']" @submit.prevent="updatePage();updateQueryString();fetchData()">
+      <form
+        :class="['ui', {'loading': isLoading}, 'form']"
+        @submit.prevent="updatePage();updateQueryString();fetchData()"
+      >
         <div class="fields">
           <div class="field">
             <label for="albums-search">
               <translate translate-context="Content/Search/Input.Label/Noun">Search</translate>
             </label>
             <div class="ui action input">
-              <input id="albums-search" type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
-              <button class="ui icon button" type="submit" :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')">
-                <i class="search icon"></i>
+              <input
+                id="albums-search"
+                v-model="query"
+                type="text"
+                name="search"
+                :placeholder="labels.searchPlaceholder"
+              >
+              <button
+                class="ui icon button"
+                type="submit"
+                :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')"
+              >
+                <i class="search icon" />
               </button>
             </div>
           </div>
           <div class="field">
             <label for="tags-search"><translate translate-context="*/*/*/Noun">Tags</translate></label>
-            <tags-selector v-model="tags"></tags-selector>
+            <tags-selector v-model="tags" />
           </div>
           <div class="field">
             <label for="album-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-            <select id="album-ordering" class="ui dropdown" v-model="ordering">
-              <option v-for="option in orderingOptions" :value="option[0]">
+            <select
+              id="album-ordering"
+              v-model="ordering"
+              class="ui dropdown"
+            >
+              <option
+                v-for="(option, key) in orderingOptions"
+                :key="key"
+                :value="option[0]"
+              >
                 {{ sharedLabels.filters[option[1]] }}
               </option>
             </select>
           </div>
           <div class="field">
             <label for="album-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
-            <select id="album-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-              <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-              <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+            <select
+              id="album-ordering-direction"
+              v-model="orderingDirection"
+              class="ui dropdown"
+            >
+              <option value="+">
+                <translate translate-context="Content/Search/Dropdown">
+                  Ascending
+                </translate>
+              </option>
+              <option value="-">
+                <translate translate-context="Content/Search/Dropdown">
+                  Descending
+                </translate>
+              </option>
             </select>
           </div>
           <div class="field">
             <label for="album-results"><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label>
-            <select id="album-results" class="ui dropdown" v-model="paginateBy">
-              <option :value="parseInt(12)">12</option>
-              <option :value="parseInt(25)">25</option>
-              <option :value="parseInt(50)">50</option>
+            <select
+              id="album-results"
+              v-model="paginateBy"
+              class="ui dropdown"
+            >
+              <option :value="parseInt(12)">
+                12
+              </option>
+              <option :value="parseInt(25)">
+                25
+              </option>
+              <option :value="parseInt(50)">
+                50
+              </option>
             </select>
           </div>
         </div>
       </form>
-      <div class="ui hidden divider"></div>
+      <div class="ui hidden divider" />
       <div
         v-if="result"
         transition-duration="0"
         item-selector=".column"
         percent-position="true"
         stagger="0"
-        class="">
+        class=""
+      >
         <div
           v-if="result.results.length > 0"
-          class="ui app-cards cards">
+          class="ui app-cards cards"
+        >
           <album-card
             v-for="album in result.results"
             :key="album.id"
-            :album="album"></album-card>
+            :album="album"
+          />
         </div>
-        <div v-else class="ui placeholder segment sixteen wide column" style="text-align: center; display: flex; align-items: center">
+        <div
+          v-else
+          class="ui placeholder segment sixteen wide column"
+          style="text-align: center; display: flex; align-items: center"
+        >
           <div class="ui icon header">
-            <i class="compact disc icon"></i>
+            <i class="compact disc icon" />
             <translate translate-context="Content/Albums/Placeholder">
               No results matching your query
             </translate>
           </div>
           <router-link
-          v-if="$store.state.auth.authenticated"
-          :to="{name: 'content.index'}"
-          class="ui success button labeled icon">
-          <i class="upload icon"></i>
+            v-if="$store.state.auth.authenticated"
+            :to="{name: 'content.index'}"
+            class="ui success button labeled icon"
+          >
+            <i class="upload icon" />
             <translate translate-context="Content/*/Verb">
               Add some music
             </translate>
@@ -83,11 +136,11 @@
       <div class="ui center aligned basic segment">
         <pagination
           v-if="result && result.count > paginateBy"
-          @page-changed="selectPage"
           :current="page"
           :paginate-by="paginateBy"
           :total="result.count"
-          ></pagination>
+          @page-changed="selectPage"
+        />
       </div>
     </section>
   </main>
@@ -95,121 +148,120 @@
 
 <script>
 import qs from 'qs'
-import axios from "axios"
-import _ from "@/lodash"
-import $ from "jquery"
+import axios from 'axios'
+import $ from 'jquery'
 
-import logger from "@/logging"
+import logger from '@/logging'
 
-import OrderingMixin from "@/components/mixins/Ordering"
-import PaginationMixin from "@/components/mixins/Pagination"
-import TranslationsMixin from "@/components/mixins/Translations"
-import AlbumCard from "@/components/audio/album/Card"
-import Pagination from "@/components/Pagination"
+import OrderingMixin from '@/components/mixins/Ordering'
+import PaginationMixin from '@/components/mixins/Pagination'
+import TranslationsMixin from '@/components/mixins/Translations'
+import AlbumCard from '@/components/audio/album/Card'
+import Pagination from '@/components/Pagination'
 import TagsSelector from '@/components/library/TagsSelector'
 
-const FETCH_URL = "albums/"
+const FETCH_URL = 'albums/'
 
 export default {
-  mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
-  props: {
-    defaultQuery: { type: String, required: false, default: "" },
-    defaultTags: { type: Array, required: false, default: () => { return [] } },
-    scope: { type: String, required: false, default: "all" },
-  },
   components: {
     AlbumCard,
     Pagination,
-    TagsSelector,
+    TagsSelector
   },
-  data() {
+  mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
+  props: {
+    defaultQuery: { type: String, required: false, default: '' },
+    defaultTags: { type: Array, required: false, default: () => { return [] } },
+    scope: { type: String, required: false, default: 'all' }
+  },
+  data () {
     return {
       isLoading: true,
       result: null,
       page: parseInt(this.defaultPage),
       query: this.defaultQuery,
       tags: (this.defaultTags || []).filter((t) => { return t.length > 0 }),
-      orderingOptions: [["creation_date", "creation_date"], ["title", "album_title"],["release_date","release_date"]]
+      orderingOptions: [['creation_date', 'creation_date'], ['title', 'album_title'], ['release_date', 'release_date']]
     }
   },
-  created() {
-    this.fetchData()
-  },
-  mounted() {
-    $(".ui.dropdown").dropdown()
-  },
   computed: {
-    labels() {
-      let searchPlaceholder = this.$pgettext('Content/Search/Input.Placeholder', "Enter album title…")
-      let title = this.$pgettext('*/*/*', "Albums")
+    labels () {
+      const searchPlaceholder = this.$pgettext('Content/Search/Input.Placeholder', 'Enter album title…')
+      const title = this.$pgettext('*/*/*', 'Albums')
       return {
         searchPlaceholder,
         title
       }
     }
   },
+  watch: {
+    page () {
+      this.updateQueryString()
+      this.fetchData()
+    },
+    '$store.state.moderation.lastUpdate': function () {
+      this.fetchData()
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  mounted () {
+    $('.ui.dropdown').dropdown()
+  },
   methods: {
-    updateQueryString: function() {
+    updateQueryString: function () {
       history.pushState(
         {},
         null,
         this.$route.path + '?' + new URLSearchParams(
           {
-          query: this.query,
-          page: this.page,
-          tag: this.tags,
-          paginateBy: this.paginateBy,
-          ordering: this.getOrderingAsString()
-        }).toString()
+            query: this.query,
+            page: this.page,
+            tag: this.tags,
+            paginateBy: this.paginateBy,
+            ordering: this.getOrderingAsString()
+          }).toString()
       )
     },
-    fetchData: function() {
-      var self = this
+    fetchData: function () {
+      const self = this
       this.isLoading = true
-      let url = FETCH_URL
-      let params = {
+      const url = FETCH_URL
+      const params = {
         scope: this.scope,
         page: this.page,
         page_size: this.paginateBy,
         q: this.query,
         ordering: this.getOrderingAsString(),
-        playable: "true",
+        playable: 'true',
         tag: this.tags,
-        include_channels: "true",
-        content_category: "music"
+        include_channels: 'true',
+        content_category: 'music'
       }
-      logger.default.debug("Fetching albums")
+      logger.default.debug('Fetching albums')
       axios.get(
         url,
         {
           params: params,
-          paramsSerializer: function(params) {
+          paramsSerializer: function (params) {
             return qs.stringify(params, { indices: false })
           }
         }
       ).then(response => {
         self.result = response.data
         self.isLoading = false
-      }, error => {
+      }, () => {
         self.result = null
         self.isLoading = false
       })
     },
-    selectPage: function(page) {
+    selectPage: function (page) {
       this.page = page
     },
-    updatePage() {
+    updatePage () {
       this.page = this.defaultPage
     }
-  },
-  watch: {
-    page() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    "$store.state.moderation.lastUpdate": function () {
-      this.fetchData()
-    }
   }
 }
 </script>
diff --git a/front/src/components/library/ArtistBase.vue b/front/src/components/library/ArtistBase.vue
index ebc7fe5c1..cbc7d4518 100644
--- a/front/src/components/library/ArtistBase.vue
+++ b/front/src/components/library/ArtistBase.vue
@@ -1,119 +1,195 @@
 <template>
   <main v-title="labels.title">
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object && !isLoading">
-      <section :class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle" v-title="object.name">
+      <section
+        v-title="object.name"
+        :class="['ui', 'head', {'with-background': cover}, 'vertical', 'center', 'aligned', 'stripe', 'segment']"
+        :style="headerStyle"
+      >
         <div class="segment-content">
           <h2 class="ui center aligned icon header">
-            <i class="circular inverted users violet icon"></i>
+            <i class="circular inverted users violet icon" />
             <div class="content">
               {{ object.name }}
-              <div class="sub header" v-if="albums">
-                <translate translate-context="Content/Artist/Paragraph"
+              <div
+                v-if="albums"
+                class="sub header"
+              >
+                <translate
+                  translate-context="Content/Artist/Paragraph"
                   tag="div"
                   translate-plural="%{ count } tracks in %{ albumsCount } albums"
                   :translate-n="totalTracks"
-                  :translate-params="{count: totalTracks, albumsCount: totalAlbums}">
+                  :translate-params="{count: totalTracks, albumsCount: totalAlbums}"
+                >
                   %{ count } track in %{ albumsCount } albums
                 </translate>
               </div>
             </div>
           </h2>
-          <tags-list v-if="object.tags && object.tags.length > 0" :tags="object.tags"></tags-list>
-          <div class="ui hidden divider"></div>
+          <tags-list
+            v-if="object.tags && object.tags.length > 0"
+            :tags="object.tags"
+          />
+          <div class="ui hidden divider" />
           <div class="header-buttons">
             <div class="ui buttons">
-              <radio-button type="artist" :object-id="object.id"></radio-button>
-
+              <radio-button
+                type="artist"
+                :object-id="object.id"
+              />
             </div>
             <div class="ui buttons">
-              <play-button :is-playable="isPlayable" class="vibrant" :artist="object">
-                <translate translate-context="Content/Artist/Button.Label/Verb">Play all albums</translate>
+              <play-button
+                :is-playable="isPlayable"
+                class="vibrant"
+                :artist="object"
+              >
+                <translate translate-context="Content/Artist/Button.Label/Verb">
+                  Play all albums
+                </translate>
               </play-button>
             </div>
 
-            <modal :show.sync="showEmbedModal" v-if="publicLibraries.length > 0">
+            <modal
+              v-if="publicLibraries.length > 0"
+              :show.sync="showEmbedModal"
+            >
               <h4 class="header">
-                <translate translate-context="Popup/Artist/Title/Verb">Embed this artist work on your website</translate>
+                <translate translate-context="Popup/Artist/Title/Verb">
+                  Embed this artist work on your website
+                </translate>
               </h4>
               <div class="scrolling content">
                 <div class="description">
-                  <embed-wizard type="artist" :id="object.id" />
-
+                  <embed-wizard
+                    :id="object.id"
+                    type="artist"
+                  />
                 </div>
               </div>
               <div class="actions">
                 <button class="ui deny button">
-                  <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+                  <translate translate-context="*/*/Button.Label/Verb">
+                    Cancel
+                  </translate>
                 </button>
               </div>
             </modal>
             <div class="ui buttons">
-              <button class="ui button" @click="$refs.dropdown.click()">
-                <translate translate-context="*/*/Button.Label/Noun">More…</translate>
+              <button
+                class="ui button"
+                @click="$refs.dropdown.click()"
+              >
+                <translate translate-context="*/*/Button.Label/Noun">
+                  More…
+                </translate>
               </button>
-              <button class="ui floating dropdown icon button" ref="dropdown" v-dropdown>
-                <i class="dropdown icon"></i>
+              <button
+                ref="dropdown"
+                v-dropdown
+                class="ui floating dropdown icon button"
+              >
+                <i class="dropdown icon" />
                 <div class="menu">
                   <a
-                    :href="object.fid"
                     v-if="domain != $store.getters['instance/domain']"
+                    :href="object.fid"
                     target="_blank"
-                    class="basic item">
-                    <i class="external icon"></i>
-                    <translate :translate-params="{domain: domain}" translate-context="Content/*/Button.Label/Verb">View on %{ domain }</translate>
+                    class="basic item"
+                  >
+                    <i class="external icon" />
+                    <translate
+                      :translate-params="{domain: domain}"
+                      translate-context="Content/*/Button.Label/Verb"
+                    >View on %{ domain }</translate>
                   </a>
 
                   <button
-                    role="button"
                     v-if="publicLibraries.length > 0"
+                    role="button"
+                    class="basic item"
                     @click.prevent="showEmbedModal = !showEmbedModal"
-                    class="basic item">
-                    <i class="code icon"></i>
-                    <translate translate-context="Content/*/Button.Label/Verb">Embed</translate>
+                  >
+                    <i class="code icon" />
+                    <translate translate-context="Content/*/Button.Label/Verb">
+                      Embed
+                    </translate>
                   </button>
-                  <a :href="wikipediaUrl" target="_blank" rel="noreferrer noopener" class="basic item">
-                    <i class="wikipedia w icon"></i>
+                  <a
+                    :href="wikipediaUrl"
+                    target="_blank"
+                    rel="noreferrer noopener"
+                    class="basic item"
+                  >
+                    <i class="wikipedia w icon" />
                     <translate translate-context="Content/*/Button.Label/Verb">Search on Wikipedia</translate>
                   </a>
-                  <a v-if="musicbrainzUrl" :href="musicbrainzUrl" target="_blank" rel="noreferrer noopener" class="basic item">
-                    <i class="external icon"></i>
+                  <a
+                    v-if="musicbrainzUrl"
+                    :href="musicbrainzUrl"
+                    target="_blank"
+                    rel="noreferrer noopener"
+                    class="basic item"
+                  >
+                    <i class="external icon" />
                     <translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate>
                   </a>
-		  <a :href="discogsUrl" target="_blank" rel="noreferrer noopener" class="basic item">
-		    <i class="external icon"></i>
-		    <translate translate-context="Content/*/Button.Label/Verb">Search on Discogs</translate>
-		  </a>
+                  <a
+                    :href="discogsUrl"
+                    target="_blank"
+                    rel="noreferrer noopener"
+                    class="basic item"
+                  >
+                    <i class="external icon" />
+                    <translate translate-context="Content/*/Button.Label/Verb">Search on Discogs</translate>
+                  </a>
                   <router-link
                     v-if="object.is_local"
                     :to="{name: 'library.artists.edit', params: {id: object.id }}"
-                    class="basic item">
-                    <i class="edit icon"></i>
-                    <translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
+                    class="basic item"
+                  >
+                    <i class="edit icon" />
+                    <translate translate-context="Content/*/Button.Label/Verb">
+                      Edit
+                    </translate>
                   </router-link>
-                  <div class="divider"></div>
+                  <div class="divider" />
                   <div
-                    role="button"
-                    class="basic item"
                     v-for="obj in getReportableObjs({artist: object})"
                     :key="obj.target.type + obj.target.id"
-                    @click.stop.prevent="$store.dispatch('moderation/report', obj.target)">
+                    role="button"
+                    class="basic item"
+                    @click.stop.prevent="$store.dispatch('moderation/report', obj.target)"
+                  >
                     <i class="share icon" /> {{ obj.label }}
                   </div>
 
-                  <div class="divider"></div>
-                  <router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.artists.detail', params: {id: object.id}}">
-                    <i class="wrench icon"></i>
-                    <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
+                  <div class="divider" />
+                  <router-link
+                    v-if="$store.state.auth.availablePermissions['library']"
+                    class="basic item"
+                    :to="{name: 'manage.library.artists.detail', params: {id: object.id}}"
+                  >
+                    <i class="wrench icon" />
+                    <translate translate-context="Content/Moderation/Link">
+                      Open in moderation interface
+                    </translate>
                   </router-link>
                   <a
                     v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                     class="basic item"
                     :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/artist/${object.id}`)"
-                    target="_blank" rel="noopener noreferrer">
-                    <i class="wrench icon"></i>
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >
+                    <i class="wrench icon" />
                     <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                   </a>
                 </div>
@@ -123,44 +199,43 @@
         </div>
       </section>
       <router-view
+        :key="$route.fullPath"
         :tracks="tracks"
         :next-tracks-url="nextTracksUrl"
         :next-albums-url="nextAlbumsUrl"
         :albums="albums"
         :is-loading-albums="isLoadingAlbums"
+        :object="object"
+        object-type="artist"
         @libraries-loaded="libraries = $event"
-        :object="object" object-type="artist"
-        :key="$route.fullPath"></router-view>
+      />
     </template>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import logger from "@/logging"
-import backend from "@/audio/backend"
-import PlayButton from "@/components/audio/PlayButton"
-import EmbedWizard from "@/components/audio/EmbedWizard"
+import axios from 'axios'
+import logger from '@/logging'
+import PlayButton from '@/components/audio/PlayButton'
+import EmbedWizard from '@/components/audio/EmbedWizard'
 import Modal from '@/components/semantic/Modal'
-import RadioButton from "@/components/radios/Button"
-import TagsList from "@/components/tags/List"
+import RadioButton from '@/components/radios/Button'
+import TagsList from '@/components/tags/List'
 import ReportMixin from '@/components/mixins/Report'
 
-import {getDomain} from '@/utils'
-
-const FETCH_URL = "albums/"
+import { getDomain } from '@/utils'
 
 export default {
-  mixins: [ReportMixin],
-  props: ["id"],
   components: {
     PlayButton,
     EmbedWizard,
     Modal,
     RadioButton,
-    TagsList,
+    TagsList
   },
-  data() {
+  mixins: [ReportMixin],
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       isLoading: true,
       isLoadingAlbums: true,
@@ -172,47 +247,7 @@ export default {
       nextAlbumsUrl: null,
       nextTracksUrl: null,
       totalAlbums: null,
-      totalTracks: null,
-    }
-  },
-  async created() {
-    await this.fetchData()
-  },
-  methods: {
-    async fetchData() {
-      var self = this
-      this.isLoading = true
-      logger.default.debug('Fetching artist "' + this.id + '"')
-
-      let artistPromise = axios.get("artists/" + this.id + "/", {params: {refresh: 'true'}}).then(response => {
-        if (response.data.channel) {
-          self.$router.replace({name: 'channels.detail', params: {id: response.data.channel.uuid}})
-        } else {
-          self.object = response.data
-        }
-      })
-      await artistPromise
-      if (!self.object) {
-        return
-      }
-      let trackPromise = axios.get("tracks/", { params: { artist: this.id, hidden: '', ordering: "-creation_date" } }).then(response => {
-        self.tracks = response.data.results
-        self.nextTracksUrl = response.data.next
-        self.totalTracks = response.data.count
-      })
-      let albumPromise = axios.get("albums/", {
-        params: { artist: self.id, ordering: "-release_date", hidden: '' }
-      }).then(response => {
-        self.nextAlbumsUrl = response.data.next
-        self.totalAlbums = response.data.count
-        let parsed = JSON.parse(JSON.stringify(response.data.results))
-        self.albums = parsed
-
-      })
-      await trackPromise
-      await albumPromise
-      self.isLoadingAlbums = false
-      self.isLoading = false
+      totalTracks: null
     }
   },
   computed: {
@@ -220,37 +255,39 @@ export default {
       if (this.object) {
         return getDomain(this.object.fid)
       }
+      return null
     },
-    isPlayable() {
+    isPlayable () {
       return (
         this.object.albums.filter(a => {
           return a.is_playable
         }).length > 0
       )
     },
-    labels() {
+    labels () {
       return {
         title: this.$pgettext('*/*/*', 'Album')
       }
     },
-    wikipediaUrl() {
+    wikipediaUrl () {
       return (
-        "https://en.wikipedia.org/w/index.php?search=" +
+        'https://en.wikipedia.org/w/index.php?search=' +
         encodeURI(this.object.name)
       )
     },
-    musicbrainzUrl() {
+    musicbrainzUrl () {
       if (this.object.mbid) {
-        return "https://musicbrainz.org/artist/" + this.object.mbid
+        return 'https://musicbrainz.org/artist/' + this.object.mbid
       }
+      return null
     },
-    discogsUrl() {
+    discogsUrl () {
       return (
-        "https://discogs.com/search/?type=artist&title=" +
-	encodeURI(this.object.name)
+        'https://discogs.com/search/?type=artist&title=' +
+        encodeURI(this.object.name)
       )
     },
-    cover() {
+    cover () {
       if (this.object.cover && this.object.cover.urls.original) {
         return this.object.cover
       }
@@ -268,27 +305,65 @@ export default {
         return l.privacy_level === 'everyone'
       })
     },
-    headerStyle() {
+    headerStyle () {
       if (!this.cover || !this.cover.urls.original) {
-        return ""
+        return ''
       }
       return (
-        "background-image: url(" +
-        this.$store.getters["instance/absoluteUrl"](this.cover.urls.original) +
-        ")"
+        'background-image: url(' +
+        this.$store.getters['instance/absoluteUrl'](this.cover.urls.original) +
+        ')'
       )
     },
     contentFilter () {
-      let self = this
       return this.$store.getters['moderation/artistFilters']().filter((e) => {
         return e.target.id === this.object.id
       })[0]
     }
   },
   watch: {
-    id() {
+    id () {
       this.fetchData()
     }
+  },
+  async created () {
+    await this.fetchData()
+  },
+  methods: {
+    async fetchData () {
+      const self = this
+      this.isLoading = true
+      logger.default.debug('Fetching artist "' + this.id + '"')
+
+      const artistPromise = axios.get('artists/' + this.id + '/', { params: { refresh: 'true' } }).then(response => {
+        if (response.data.channel) {
+          self.$router.replace({ name: 'channels.detail', params: { id: response.data.channel.uuid } })
+        } else {
+          self.object = response.data
+        }
+      })
+      await artistPromise
+      if (!self.object) {
+        return
+      }
+      const trackPromise = axios.get('tracks/', { params: { artist: this.id, hidden: '', ordering: '-creation_date' } }).then(response => {
+        self.tracks = response.data.results
+        self.nextTracksUrl = response.data.next
+        self.totalTracks = response.data.count
+      })
+      const albumPromise = axios.get('albums/', {
+        params: { artist: self.id, ordering: '-release_date', hidden: '' }
+      }).then(response => {
+        self.nextAlbumsUrl = response.data.next
+        self.totalAlbums = response.data.count
+        const parsed = JSON.parse(JSON.stringify(response.data.results))
+        self.albums = parsed
+      })
+      await trackPromise
+      await albumPromise
+      self.isLoadingAlbums = false
+      self.isLoading = false
+    }
   }
 }
 </script>
diff --git a/front/src/components/library/ArtistDetail.vue b/front/src/components/library/ArtistDetail.vue
index 3216842b9..a1bab20f4 100644
--- a/front/src/components/library/ArtistDetail.vue
+++ b/front/src/components/library/ArtistDetail.vue
@@ -1,69 +1,127 @@
 <template>
   <div v-if="object">
-    <div class="ui small text container" v-if="contentFilter">
-      <div class="ui hidden divider"></div>
+    <div
+      v-if="contentFilter"
+      class="ui small text container"
+    >
+      <div class="ui hidden divider" />
       <div class="ui message">
         <p>
-          <translate translate-context="Content/Artist/Paragraph">You are currently hiding content related to this artist.</translate>
+          <translate translate-context="Content/Artist/Paragraph">
+            You are currently hiding content related to this artist.
+          </translate>
         </p>
-        <router-link class="right floated" :to="{name: 'settings'}">
-          <translate translate-context="Content/Moderation/Link">Review my filters</translate>
+        <router-link
+          class="right floated"
+          :to="{name: 'settings'}"
+        >
+          <translate translate-context="Content/Moderation/Link">
+            Review my filters
+          </translate>
         </router-link>
-        <button @click="$store.dispatch('moderation/deleteContentFilter', contentFilter.uuid)" class="ui basic tiny button">
-          <translate translate-context="Content/Moderation/Button.Label">Remove filter</translate>
+        <button
+          class="ui basic tiny button"
+          @click="$store.dispatch('moderation/deleteContentFilter', contentFilter.uuid)"
+        >
+          <translate translate-context="Content/Moderation/Button.Label">
+            Remove filter
+          </translate>
         </button>
       </div>
     </div>
-    <section v-if="tracks.length > 0" class="ui vertical stripe segment">
-      <track-table :is-artist="true" :show-position="false" :track-only="true" :tracks="tracks.slice(0,5)">
+    <section
+      v-if="tracks.length > 0"
+      class="ui vertical stripe segment"
+    >
+      <track-table
+        :is-artist="true"
+        :show-position="false"
+        :track-only="true"
+        :tracks="tracks.slice(0,5)"
+      >
         <template slot="header">
           <h2>
-            <translate translate-context="Content/Artist/Title">New tracks by this artist</translate>
+            <translate translate-context="Content/Artist/Title">
+              New tracks by this artist
+            </translate>
           </h2>
-          <div class="ui hidden divider"></div>
+          <div class="ui hidden divider" />
         </template>
       </track-table>
     </section>
-    <section v-if="isLoadingAlbums" class="ui vertical stripe segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <section
+      v-if="isLoadingAlbums"
+      class="ui vertical stripe segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </section>
-    <section v-else-if="albums && albums.length > 0" class="ui vertical stripe segment">
+    <section
+      v-else-if="albums && albums.length > 0"
+      class="ui vertical stripe segment"
+    >
       <h2>
-        <translate translate-context="Content/Artist/Title">Albums by this artist</translate>
+        <translate translate-context="Content/Artist/Title">
+          Albums by this artist
+        </translate>
       </h2>
       <div class="ui cards app-cards">
-        <album-card :album="album" :key="album.id" v-for="album in allAlbums"></album-card>
+        <album-card
+          v-for="album in allAlbums"
+          :key="album.id"
+          :album="album"
+        />
       </div>
-      <div class="ui hidden divider"></div>
-      <button :class="['ui', {loading: isLoadingMoreAlbums}, 'button']" v-if="nextAlbumsUrl && loadMoreAlbumsUrl" @click="loadMoreAlbums(loadMoreAlbumsUrl)">
-        <translate translate-context="Content/*/Button.Label">Load more…</translate>
+      <div class="ui hidden divider" />
+      <button
+        v-if="nextAlbumsUrl && loadMoreAlbumsUrl"
+        :class="['ui', {loading: isLoadingMoreAlbums}, 'button']"
+        @click="loadMoreAlbums(loadMoreAlbumsUrl)"
+      >
+        <translate translate-context="Content/*/Button.Label">
+          Load more…
+        </translate>
       </button>
     </section>
     <section class="ui vertical stripe segment">
       <h2>
-        <translate translate-context="Content/*/Title/Noun">User libraries</translate>
+        <translate translate-context="Content/*/Title/Noun">
+          User libraries
+        </translate>
       </h2>
-      <library-widget @loaded="$emit('libraries-loaded', $event)" :url="'artists/' + object.id + '/libraries/'">
-        <translate translate-context="Content/Artist/Paragraph" slot="subtitle">This artist is present in the following libraries:</translate>
+      <library-widget
+        :url="'artists/' + object.id + '/libraries/'"
+        @loaded="$emit('libraries-loaded', $event)"
+      >
+        <translate
+          slot="subtitle"
+          translate-context="Content/Artist/Paragraph"
+        >
+          This artist is present in the following libraries:
+        </translate>
       </library-widget>
     </section>
   </div>
 </template>
 
 <script>
-import _ from "@/lodash"
-import axios from "axios"
-import logger from "@/logging"
-import AlbumCard from "@/components/audio/album/Card"
-import TrackTable from "@/components/audio/track/Table"
-import LibraryWidget from "@/components/federation/LibraryWidget"
+import axios from 'axios'
+import AlbumCard from '@/components/audio/album/Card'
+import TrackTable from '@/components/audio/track/Table'
+import LibraryWidget from '@/components/federation/LibraryWidget'
 
 export default {
-  props: ["object", "tracks", "albums", "isLoadingAlbums", "nextTracksUrl", "nextAlbumsUrl"],
   components: {
     AlbumCard,
     TrackTable,
-    LibraryWidget,
+    LibraryWidget
+  },
+  props: {
+    object: { type: Object, required: true },
+    tracks: { type: Array, required: true },
+    albums: { type: Array, required: true },
+    isLoadingAlbums: { type: Boolean, required: true },
+    nextTracksUrl: { type: String, required: true },
+    nextAlbumsUrl: { type: String, required: true }
   },
   data () {
     return {
@@ -74,26 +132,24 @@ export default {
   },
   computed: {
     contentFilter () {
-      let self = this
       return this.$store.getters['moderation/artistFilters']().filter((e) => {
         return e.target.id === this.object.id
       })[0]
     },
-     allAlbums () {
+    allAlbums () {
       return this.albums.concat(this.additionalAlbums)
     }
   },
   methods: {
     loadMoreAlbums (url) {
-      let self = this
+      const self = this
       self.isLoadingMoreAlbums = true
       axios.get(url).then((response) => {
         self.additionalAlbums = self.additionalAlbums.concat(response.data.results)
         self.loadMoreAlbumsUrl = response.data.next
         self.isLoadingMoreAlbums = false
-      }, (error) => {
+      }, () => {
         self.isLoadingMoreAlbums = false
-
       })
     }
   }
diff --git a/front/src/components/library/ArtistEdit.vue b/front/src/components/library/ArtistEdit.vue
index 80a9ae0c3..976918ab3 100644
--- a/front/src/components/library/ArtistEdit.vue
+++ b/front/src/components/library/ArtistEdit.vue
@@ -1,37 +1,56 @@
 <template>
-
   <section class="ui vertical stripe segment">
     <div class="ui text container">
       <h2>
-        <translate v-if="canEdit" key="1" translate-context="Content/*/Title">Edit this artist</translate>
-        <translate v-else key="2" translate-context="Content/*/Title">Suggest an edit on this artist</translate>
+        <translate
+          v-if="canEdit"
+          key="1"
+          translate-context="Content/*/Title"
+        >
+          Edit this artist
+        </translate>
+        <translate
+          v-else
+          key="2"
+          translate-context="Content/*/Title"
+        >
+          Suggest an edit on this artist
+        </translate>
       </h2>
-      <div class="ui message" v-if="!object.is_local">
-        <translate translate-context="Content/*/Message">This object is managed by another server, you cannot edit it.</translate>
+      <div
+        v-if="!object.is_local"
+        class="ui message"
+      >
+        <translate translate-context="Content/*/Message">
+          This object is managed by another server, you cannot edit it.
+        </translate>
       </div>
       <edit-form
         v-else
         :object-type="objectType"
         :object="object"
-        :can-edit="canEdit"></edit-form>
+        :can-edit="canEdit"
+      />
     </div>
   </section>
 </template>
 
 <script>
-import axios from "axios"
-
 import EditForm from '@/components/library/EditForm'
 export default {
-  props: ["objectType", "object", "libraries"],
-  data() {
-    return {
-      id: this.object.id,
-    }
-  },
   components: {
     EditForm
   },
+  props: {
+    objectType: { type: String, required: true },
+    object: { type: Object, required: true },
+    libraries: { type: Array, required: true }
+  },
+  data () {
+    return {
+      id: this.object.id
+    }
+  },
   computed: {
     canEdit () {
       return true
diff --git a/front/src/components/library/Artists.vue b/front/src/components/library/Artists.vue
index 6bf7eaa60..4ee16993b 100644
--- a/front/src/components/library/Artists.vue
+++ b/front/src/components/library/Artists.vue
@@ -2,67 +2,138 @@
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
       <h2 class="ui header">
-        <translate translate-context="Content/Artist/Title">Browsing artists</translate>
+        <translate translate-context="Content/Artist/Title">
+          Browsing artists
+        </translate>
       </h2>
-      <form :class="['ui', {'loading': isLoading}, 'form']" @submit.prevent="updatePage();updateQueryString();fetchData()">
+      <form
+        :class="['ui', {'loading': isLoading}, 'form']"
+        @submit.prevent="updatePage();updateQueryString();fetchData()"
+      >
         <div class="fields">
           <div class="field">
             <label for="artist-search">
               <translate translate-context="Content/Search/Input.Label/Noun">Artist name</translate>
             </label>
             <div class="ui action input">
-              <input id="artist-search" type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
-              <button class="ui icon button" type="submit" :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')">
-                <i class="search icon"></i>
+              <input
+                id="artist-search"
+                v-model="query"
+                type="text"
+                name="search"
+                :placeholder="labels.searchPlaceholder"
+              >
+              <button
+                class="ui icon button"
+                type="submit"
+                :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')"
+              >
+                <i class="search icon" />
               </button>
             </div>
           </div>
           <div class="field">
             <label for="tags-search"><translate translate-context="*/*/*/Noun">Tags</translate></label>
-            <tags-selector v-model="tags"></tags-selector>
+            <tags-selector v-model="tags" />
           </div>
           <div class="field">
             <label for="artist-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-            <select id="artist-ordering" class="ui dropdown" v-model="ordering">
-              <option v-for="option in orderingOptions" :value="option[0]">
+            <select
+              id="artist-ordering"
+              v-model="ordering"
+              class="ui dropdown"
+            >
+              <option
+                v-for="(option, key) in orderingOptions"
+                :key="key"
+                :value="option[0]"
+              >
                 {{ sharedLabels.filters[option[1]] }}
               </option>
             </select>
           </div>
           <div class="field">
             <label for="artist-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
-            <select id="artist-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-              <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-              <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+            <select
+              id="artist-ordering-direction"
+              v-model="orderingDirection"
+              class="ui dropdown"
+            >
+              <option value="+">
+                <translate translate-context="Content/Search/Dropdown">
+                  Ascending
+                </translate>
+              </option>
+              <option value="-">
+                <translate translate-context="Content/Search/Dropdown">
+                  Descending
+                </translate>
+              </option>
             </select>
           </div>
           <div class="field">
             <label for="artist-results"><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label>
-            <select id="artist-results" class="ui dropdown" v-model="paginateBy">
-              <option :value="parseInt(12)">12</option>
-              <option :value="parseInt(30)">30</option>
-              <option :value="parseInt(50)">50</option>
+            <select
+              id="artist-results"
+              v-model="paginateBy"
+              class="ui dropdown"
+            >
+              <option :value="parseInt(12)">
+                12
+              </option>
+              <option :value="parseInt(30)">
+                30
+              </option>
+              <option :value="parseInt(50)">
+                50
+              </option>
             </select>
           </div>
           <div class="field">
             <span id="excludeHeader">Exclude Compilation Artists</span>
-              <div id="excludeCompilation" class="ui toggle checkbox">
-              <input id="exclude-compilation" v-model="excludeCompilation" true-value="true" false-value="null" type="checkbox">
-              <label for="exclude-compilation" class="visually-hidden"><translate translate-context="Content/Search/Checkbox/Noun">Exclude Compilation Artists</translate></label>
+            <div
+              id="excludeCompilation"
+              class="ui toggle checkbox"
+            >
+              <input
+                id="exclude-compilation"
+                v-model="excludeCompilation"
+                true-value="true"
+                false-value="null"
+                type="checkbox"
+              >
+              <label
+                for="exclude-compilation"
+                class="visually-hidden"
+              ><translate translate-context="Content/Search/Checkbox/Noun">Exclude Compilation Artists</translate></label>
             </div>
           </div>
         </div>
       </form>
-      <div class="ui hidden divider"></div>
-      <div v-if="result && result.results.length > 0" class="ui five app-cards cards">
-        <div v-if="isLoading" class="ui inverted active dimmer">
-          <div class="ui loader"></div>
+      <div class="ui hidden divider" />
+      <div
+        v-if="result && result.results.length > 0"
+        class="ui five app-cards cards"
+      >
+        <div
+          v-if="isLoading"
+          class="ui inverted active dimmer"
+        >
+          <div class="ui loader" />
         </div>
-        <artist-card :artist="artist" v-for="artist in result.results" :key="artist.id"></artist-card>
+        <artist-card
+          v-for="artist in result.results"
+          :key="artist.id"
+          :artist="artist"
+        />
       </div>
-      <div v-else-if="!isLoading" class="ui placeholder segment sixteen wide column" style="text-align: center; display: flex; align-items: center">
+      <div
+        v-else-if="!isLoading"
+        class="ui placeholder segment sixteen wide column"
+        style="text-align: center; display: flex; align-items: center"
+      >
         <div class="ui icon header">
-          <i class="compact disc icon"></i>
+          <i class="compact disc icon" />
           <translate translate-context="Content/Artists/Placeholder">
             No results matching your query
           </translate>
@@ -70,21 +141,22 @@
         <router-link
           v-if="$store.state.auth.authenticated"
           :to="{name: 'content.index'}"
-          class="ui success button labeled icon">
-          <i class="upload icon"></i>
+          class="ui success button labeled icon"
+        >
+          <i class="upload icon" />
           <translate translate-context="Content/*/Verb">
-              Add some music
+            Add some music
           </translate>
         </router-link>
       </div>
       <div class="ui center aligned basic segment">
         <pagination
           v-if="result && result.count > paginateBy"
-          @page-changed="selectPage"
           :current="page"
           :paginate-by="paginateBy"
           :total="result.count"
-          ></pagination>
+          @page-changed="selectPage"
+        />
       </div>
     </section>
   </main>
@@ -92,34 +164,33 @@
 
 <script>
 import qs from 'qs'
-import axios from "axios"
-import _ from "@/lodash"
-import $ from "jquery"
+import axios from 'axios'
+import $ from 'jquery'
 
-import logger from "@/logging"
+import logger from '@/logging'
 
-import OrderingMixin from "@/components/mixins/Ordering"
-import PaginationMixin from "@/components/mixins/Pagination"
-import TranslationsMixin from "@/components/mixins/Translations"
-import ArtistCard from "@/components/audio/artist/Card"
-import Pagination from "@/components/Pagination"
+import OrderingMixin from '@/components/mixins/Ordering'
+import PaginationMixin from '@/components/mixins/Pagination'
+import TranslationsMixin from '@/components/mixins/Translations'
+import ArtistCard from '@/components/audio/artist/Card'
+import Pagination from '@/components/Pagination'
 import TagsSelector from '@/components/library/TagsSelector'
 
-const FETCH_URL = "artists/"
+const FETCH_URL = 'artists/'
 
 export default {
-  mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
-  props: {
-    defaultQuery: { type: String, required: false, default: "" },
-    defaultTags: { type: Array, required: false, default: () => { return [] } },
-    scope: { type: String, required: false, default: "all" },
-  },
   components: {
     ArtistCard,
     Pagination,
-    TagsSelector,
+    TagsSelector
+  },
+  mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
+  props: {
+    defaultQuery: { type: String, required: false, default: '' },
+    defaultTags: { type: Array, required: false, default: () => { return [] } },
+    scope: { type: String, required: false, default: 'all' }
   },
-  data() {
+  data () {
     return {
       isLoading: true,
       result: null,
@@ -127,93 +198,93 @@ export default {
       page: parseInt(this.defaultPage),
       query: this.defaultQuery,
       tags: (this.defaultTags || []).filter((t) => { return t.length > 0 }),
-      orderingOptions: [["creation_date", "creation_date"], ["name", "name"]]
+      orderingOptions: [['creation_date', 'creation_date'], ['name', 'name']]
     }
   },
-  created() {
-    this.fetchData()
-  },
-  mounted() {
-    $(".ui.dropdown").dropdown()
-  },
   computed: {
-    labels() {
-      let searchPlaceholder = this.$pgettext('Content/Search/Input.Placeholder', "Search…")
-      let title = this.$pgettext('*/*/*/Noun', "Artists")
+    labels () {
+      const searchPlaceholder = this.$pgettext('Content/Search/Input.Placeholder', 'Search…')
+      const title = this.$pgettext('*/*/*/Noun', 'Artists')
       return {
         searchPlaceholder,
         title
       }
     }
   },
+  watch: {
+    page () {
+      this.updateQueryString()
+      this.fetchData()
+    },
+    '$store.state.moderation.lastUpdate': function () {
+      this.fetchData()
+    },
+    excludeCompilation () {
+      this.fetchData()
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  mounted () {
+    $('.ui.dropdown').dropdown()
+  },
   methods: {
-    updateQueryString: function() {
+    updateQueryString: function () {
       history.pushState(
         {},
         null,
         this.$route.path + '?' + new URLSearchParams(
           {
-          query: this.query,
-          page: this.page,
-          tag: this.tags,
-          paginateBy: this.paginateBy,
-          ordering: this.getOrderingAsString(),
-          content_category: 'music',
-          include_channels: true,
-        }).toString()
+            query: this.query,
+            page: this.page,
+            tag: this.tags,
+            paginateBy: this.paginateBy,
+            ordering: this.getOrderingAsString(),
+            content_category: 'music',
+            include_channels: true
+          }).toString()
       )
     },
-    fetchData: function() {
-      var self = this
+    fetchData: function () {
+      const self = this
       this.isLoading = true
-      let url = FETCH_URL
-      let params = {
+      const url = FETCH_URL
+      const params = {
         scope: this.scope,
         page: this.page,
         page_size: this.paginateBy,
         has_albums: this.excludeCompilation,
         q: this.query,
         ordering: this.getOrderingAsString(),
-        playable: "true",
+        playable: 'true',
         tag: this.tags,
-        include_channels: "true",
-        content_category: 'music',
+        include_channels: 'true',
+        content_category: 'music'
       }
-      logger.default.debug("Fetching artists")
+      logger.default.debug('Fetching artists')
       axios.get(
         url,
         {
           params: params,
-          paramsSerializer: function(params) {
+          paramsSerializer: function (params) {
             return qs.stringify(params, { indices: false })
           }
         }
       ).then(response => {
         self.result = response.data
         self.isLoading = false
-      }, error => {
+      }, () => {
         self.result = null
         self.isLoading = false
       })
     },
-    selectPage: function(page) {
+    selectPage: function (page) {
       this.page = page
     },
-    updatePage() {
+    updatePage () {
       this.page = this.defaultPage
     }
-  },
-  watch: {
-    page() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    "$store.state.moderation.lastUpdate": function () {
-      this.fetchData()
-    },
-    excludeCompilation() {
-      this.fetchData()
-    }
   }
 }
 </script>
diff --git a/front/src/components/library/EditCard.vue b/front/src/components/library/EditCard.vue
index 39f3bbf99..a8fde61cf 100644
--- a/front/src/components/library/EditCard.vue
+++ b/front/src/components/library/EditCard.vue
@@ -3,82 +3,147 @@
     <div class="content">
       <h4 class="header">
         <router-link :to="detailUrl">
-          <translate translate-context="Content/Library/Card/Short" :translate-params="{id: obj.uuid.substring(0, 8)}">Modification %{ id }</translate>
+          <translate
+            translate-context="Content/Library/Card/Short"
+            :translate-params="{id: obj.uuid.substring(0, 8)}"
+          >
+            Modification %{ id }
+          </translate>
         </router-link>
       </h4>
       <div class="meta">
         <router-link
           v-if="obj.target && obj.target.type === 'track'"
-          :to="{name: 'library.tracks.detail', params: {id: obj.target.id }}">
-          <i class="music icon"></i>
-          <translate translate-context="Content/Library/Card/Short" :translate-params="{id: obj.target.id, name: obj.target.repr}">Track #%{ id } - %{ name }</translate>
+          :to="{name: 'library.tracks.detail', params: {id: obj.target.id }}"
+        >
+          <i class="music icon" />
+          <translate
+            translate-context="Content/Library/Card/Short"
+            :translate-params="{id: obj.target.id, name: obj.target.repr}"
+          >
+            Track #%{ id } - %{ name }
+          </translate>
         </router-link>
         <br>
-        <human-date :date="obj.creation_date" :icon="true"></human-date>
+        <human-date
+          :date="obj.creation_date"
+          :icon="true"
+        />
 
         <span class="right floated">
           <span v-if="obj.is_approved && obj.is_applied">
-            <i class="success check icon"></i>
+            <i class="success check icon" />
             <translate translate-context="Content/Library/Card/Short">Approved and applied</translate>
           </span>
           <span v-else-if="obj.is_approved">
-            <i class="success check icon"></i>
+            <i class="success check icon" />
             <translate translate-context="Content/*/*/Short">Approved</translate>
           </span>
           <span v-else-if="obj.is_approved === null">
-            <i class="warning hourglass icon"></i>
+            <i class="warning hourglass icon" />
             <translate translate-context="Content/Admin/*/Noun">Pending review</translate>
           </span>
           <span v-else-if="obj.is_approved === false">
-            <i class="danger x icon"></i>
+            <i class="danger x icon" />
             <translate translate-context="Content/Library/*/Short">Rejected</translate>
           </span>
         </span>
       </div>
     </div>
-    <div v-if="obj.summary" class="content">
+    <div
+      v-if="obj.summary"
+      class="content"
+    >
       {{ obj.summary }}
     </div>
     <div class="content">
-      <table v-if="obj.type === 'update'" class="ui celled very basic fixed stacking table">
+      <table
+        v-if="obj.type === 'update'"
+        class="ui celled very basic fixed stacking table"
+      >
         <thead>
           <tr>
-            <th><translate translate-context="Content/Library/Card.Table.Header/Short">Field</translate></th>
-            <th><translate translate-context="Content/Library/Card.Table.Header/Short">Old value</translate></th>
-            <th><translate translate-context="Content/Library/Card.Table.Header/Short">New value</translate></th>
+            <th>
+              <translate translate-context="Content/Library/Card.Table.Header/Short">
+                Field
+              </translate>
+            </th>
+            <th>
+              <translate translate-context="Content/Library/Card.Table.Header/Short">
+                Old value
+              </translate>
+            </th>
+            <th>
+              <translate translate-context="Content/Library/Card.Table.Header/Short">
+                New value
+              </translate>
+            </th>
           </tr>
         </thead>
         <tbody>
-          <tr v-for="field in updatedFields" :key="field.id">
+          <tr
+            v-for="field in updatedFields"
+            :key="field.id"
+          >
             <td>{{ field.id }}</td>
 
             <td v-if="field.diff">
               <template v-if="field.config.type === 'attachment' && field.oldRepr">
-                <img class="ui image" alt="" :src="$store.getters['instance/absoluteUrl'](`api/v1/attachments/${field.oldRepr}/proxy?next=medium_square_crop`)" />
+                <img
+                  class="ui image"
+                  alt=""
+                  :src="$store.getters['instance/absoluteUrl'](`api/v1/attachments/${field.oldRepr}/proxy?next=medium_square_crop`)"
+                >
               </template>
               <template v-else>
-                <span v-if="!part.added" v-for="part in field.diff" :class="['diff', {removed: part.removed}]">
+                <span
+                  v-for="(part, key) in field.diff"
+                  v-if="!part.added"
+                  :key="key"
+                  :class="['diff', {removed: part.removed}]"
+                >
                   {{ part.value }}
                 </span>
               </template>
             </td>
             <td v-else>
-              <translate translate-context="*/*/*">N/A</translate>
+              <translate translate-context="*/*/*">
+                N/A
+              </translate>
             </td>
 
-            <td v-if="field.diff" :title="field.newRepr">
+            <td
+              v-if="field.diff"
+              :title="field.newRepr"
+            >
               <template v-if="field.config.type === 'attachment' && field.newRepr">
-                <img class="ui image" alt="" :src="$store.getters['instance/absoluteUrl'](`api/v1/attachments/${field.newRepr}/proxy?next=medium_square_crop`)" />
+                <img
+                  class="ui image"
+                  alt=""
+                  :src="$store.getters['instance/absoluteUrl'](`api/v1/attachments/${field.newRepr}/proxy?next=medium_square_crop`)"
+                >
               </template>
               <template v-else>
-                <span v-if="!part.removed" v-for="part in field.diff" :class="['diff', {added: part.added}]">
+                <span
+                  v-for="(part, key) in field.diff"
+                  v-if="!part.removed"
+                  :key="key"
+                  :class="['diff', {added: part.added}]"
+                >
                   {{ part.value }}
                 </span>
               </template>
             </td>
-            <td v-else :title="field.newRepr">
+            <td
+              v-else
+              :title="field.newRepr"
+            >
               <template v-if="field.config.type === 'attachment' && field.newRepr">
-                <img class="ui image" alt="" :src="$store.getters['instance/absoluteUrl'](`api/v1/attachments/${field.newRepr}/proxy?next=medium_square_crop`)" />
+                <img
+                  class="ui image"
+                  alt=""
+                  :src="$store.getters['instance/absoluteUrl'](`api/v1/attachments/${field.newRepr}/proxy?next=medium_square_crop`)"
+                >
               </template>
               <template v-else>
                 {{ field.newRepr }}
@@ -88,32 +153,59 @@
         </tbody>
       </table>
     </div>
-    <div v-if="obj.created_by" class="extra content">
+    <div
+      v-if="obj.created_by"
+      class="extra content"
+    >
       <actor-link :actor="obj.created_by" />
     </div>
-    <div v-if="canDelete || canApprove" class="ui bottom attached buttons">
+    <div
+      v-if="canDelete || canApprove"
+      class="ui bottom attached buttons"
+    >
       <button
         v-if="canApprove && obj.is_approved !== true"
+        :class="['ui', {loading: isLoading}, 'success', 'basic', 'button']"
         @click="approve(true)"
-        :class="['ui', {loading: isLoading}, 'success', 'basic', 'button']">
-        <translate translate-context="Content/*/Button.Label/Verb">Approve</translate>
+      >
+        <translate translate-context="Content/*/Button.Label/Verb">
+          Approve
+        </translate>
       </button>
       <button
         v-if="canApprove && obj.is_approved === null"
+        :class="['ui', {loading: isLoading}, 'warning', 'basic', 'button']"
         @click="approve(false)"
-        :class="['ui', {loading: isLoading}, 'warning', 'basic', 'button']">
-        <translate translate-context="Content/Library/Button.Label">Reject</translate>
+      >
+        <translate translate-context="Content/Library/Button.Label">
+          Reject
+        </translate>
       </button>
       <dangerous-button
         v-if="canDelete"
         :class="['ui', {loading: isLoading}, 'basic danger button']"
-        :action="remove">
-        <translate translate-context="*/*/*/Verb">Delete</translate>
-        <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this suggestion?</translate></p>
+        :action="remove"
+      >
+        <translate translate-context="*/*/*/Verb">
+          Delete
+        </translate>
+        <p slot="modal-header">
+          <translate translate-context="Popup/Library/Title">
+            Delete this suggestion?
+          </translate>
+        </p>
         <div slot="modal-content">
-          <p><translate translate-context="Popup/Library/Paragraph">The suggestion will be completely removed, this action is irreversible.</translate></p>
+          <p>
+            <translate translate-context="Popup/Library/Paragraph">
+              The suggestion will be completely removed, this action is irreversible.
+            </translate>
+          </p>
         </div>
-        <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p>
+        <p slot="modal-confirm">
+          <translate translate-context="*/*/*/Verb">
+            Delete
+          </translate>
+        </p>
       </dangerous-button>
     </div>
   </div>
@@ -134,8 +226,8 @@ function castValue (value) {
 
 export default {
   props: {
-    obj: {required: true},
-    currentState: {required: false}
+    obj: { type: Object, required: true },
+    currentState: { type: Object, required: false, default: function () { return { } } }
   },
   data () {
     return {
@@ -161,7 +253,7 @@ export default {
         return ''
       }
       let namespace
-      let id = this.obj.target.id
+      const id = this.obj.target.id
       if (this.obj.target.type === 'track') {
         namespace = 'library.tracks.edit.detail'
       }
@@ -171,22 +263,22 @@ export default {
       if (this.obj.target.type === 'artist') {
         namespace = 'library.artists.edit.detail'
       }
-      return this.$router.resolve({name: namespace, params: {id, editId: this.obj.uuid}}).href
+      return this.$router.resolve({ name: namespace, params: { id, editId: this.obj.uuid } }).href
     },
 
     updatedFields () {
       if (!this.obj.target) {
         return []
       }
-      let payload = this.obj.payload
-      let previousState = this.previousState
-      let fields = Object.keys(payload)
-      let self = this
+      const payload = this.obj.payload
+      const previousState = this.previousState
+      const fields = Object.keys(payload)
+      const self = this
       return fields.map((f) => {
-        let fieldConfig = edits.getFieldConfig(self.configs, this.obj.target.type, f)
-        let dummyRepr = (v) => { return v }
-        let getValueRepr = fieldConfig.getValueRepr || dummyRepr
-        let d = {
+        const fieldConfig = edits.getFieldConfig(self.configs, this.obj.target.type, f)
+        const dummyRepr = (v) => { return v }
+        const getValueRepr = fieldConfig.getValueRepr || dummyRepr
+        const d = {
           id: f,
           config: fieldConfig
         }
@@ -206,12 +298,12 @@ export default {
   },
   methods: {
     remove () {
-      let self = this
+      const self = this
       this.isLoading = true
       axios.delete(`mutations/${this.obj.uuid}/`).then((response) => {
         self.$emit('deleted')
         self.isLoading = false
-      }, error => {
+      }, () => {
         self.isLoading = false
       })
     },
@@ -222,16 +314,16 @@ export default {
       } else {
         url = `mutations/${this.obj.uuid}/reject/`
       }
-      let self = this
+      const self = this
       this.isLoading = true
       axios.post(url).then((response) => {
         self.$emit('approved', approved)
         self.isLoading = false
-        self.$store.commit('ui/incrementNotifications', {count: -1, type: 'pendingReviewEdits'})
-      }, error => {
+        self.$store.commit('ui/incrementNotifications', { count: -1, type: 'pendingReviewEdits' })
+      }, () => {
         self.isLoading = false
       })
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/library/EditDetail.vue b/front/src/components/library/EditDetail.vue
index 4a0c89434..dd617e21e 100644
--- a/front/src/components/library/EditDetail.vue
+++ b/front/src/components/library/EditDetail.vue
@@ -1,46 +1,52 @@
 <template>
-
   <section :class="['ui', 'vertical', 'stripe', {loading: isLoading}, 'segment']">
     <div class="ui text container">
-      <edit-card v-if="obj" :obj="obj" :current-state="currentState" />
+      <edit-card
+        v-if="obj"
+        :obj="obj"
+        :current-state="currentState"
+      />
     </div>
   </section>
 </template>
 
 <script>
-import axios from "axios"
+import axios from 'axios'
 import edits from '@/edits'
 import EditCard from '@/components/library/EditCard'
 export default {
-  props: ["object", "objectType", "editId"],
   components: {
     EditCard
   },
+  props: {
+    object: { type: Object, required: true },
+    objectType: { type: String, required: true },
+    editId: { type: Number, required: true }
+  },
   data () {
     return {
       isLoading: true,
-      obj: null,
+      obj: null
     }
   },
-  created () {
-    this.fetchData()
-  },
   computed: {
     configs: edits.getConfigs,
     config: edits.getConfig,
-    currentState: edits.getCurrentState,
     currentState () {
-      let self = this
-      let s = {}
+      const self = this
+      const s = {}
       this.config.fields.forEach(f => {
-        s[f.id] = {value: f.getValue(self.object)}
+        s[f.id] = { value: f.getValue(self.object) }
       })
       return s
     }
   },
+  created () {
+    this.fetchData()
+  },
   methods: {
     fetchData () {
-      var self = this
+      const self = this
       this.isLoading = true
       axios.get(`mutations/${this.editId}/`).then(response => {
         self.obj = response.data
diff --git a/front/src/components/library/EditForm.vue b/front/src/components/library/EditForm.vue
index c9b017411..60175df3d 100644
--- a/front/src/components/library/EditForm.vue
+++ b/front/src/components/library/EditForm.vue
@@ -1,24 +1,41 @@
 <template>
   <div v-if="submittedMutation">
     <div class="ui positive message">
-      <h4 class="header"><translate translate-context="Content/Library/Paragraph">Your edit was successfully submitted.</translate></h4>
+      <h4 class="header">
+        <translate translate-context="Content/Library/Paragraph">
+          Your edit was successfully submitted.
+        </translate>
+      </h4>
     </div>
-    <edit-card :obj="submittedMutation" :current-state="currentState" />
-    <button class="ui button" @click.prevent="submittedMutation = null">
+    <edit-card
+      :obj="submittedMutation"
+      :current-state="currentState"
+    />
+    <button
+      class="ui button"
+      @click.prevent="submittedMutation = null"
+    >
       <translate translate-context="Content/Library/Button.Label">
         Submit another edit
       </translate>
     </button>
   </div>
   <div v-else>
-
-    <edit-list :filters="editListFilters" :url="mutationsUrl" :obj="object" :currentState="currentState">
+    <edit-list
+      :filters="editListFilters"
+      :url="mutationsUrl"
+      :obj="object"
+      :current-state="currentState"
+    >
       <div slot="title">
         <template v-if="showPendingReview">
           <translate translate-context="Content/Library/Paragraph">
             Recent edits awaiting review
           </translate>
-          <button class="ui tiny basic right floated button" @click.prevent="showPendingReview = false">
+          <button
+            class="ui tiny basic right floated button"
+            @click.prevent="showPendingReview = false"
+          >
             <translate translate-context="Content/Library/Button.Label">
               Show all edits
             </translate>
@@ -28,7 +45,10 @@
           <translate translate-context="Content/Library/Paragraph">
             Recent edits
           </translate>
-          <button class="ui tiny basic right floated button" @click.prevent="showPendingReview = true">
+          <button
+            class="ui tiny basic right floated button"
+            @click.prevent="showPendingReview = true"
+          >
             <translate translate-context="Content/Library/Button.Label">
               Restrict to unreviewed edits
             </translate>
@@ -41,91 +61,178 @@
         </translate>
       </empty-state>
     </edit-list>
-    <form class="ui form" @submit.prevent="submit()">
-      <div class="ui hidden divider"></div>
-      <div v-if="errors.length > 0" role="alert" class="ui negative message">
-        <h4 class="header"><translate translate-context="Content/Library/Error message.Title">Error while submitting edit</translate></h4>
+    <form
+      class="ui form"
+      @submit.prevent="submit()"
+    >
+      <div class="ui hidden divider" />
+      <div
+        v-if="errors.length > 0"
+        role="alert"
+        class="ui negative message"
+      >
+        <h4 class="header">
+          <translate translate-context="Content/Library/Error message.Title">
+            Error while submitting edit
+          </translate>
+        </h4>
         <ul class="list">
-          <li v-for="error in errors">{{ error }}</li>
+          <li
+            v-for="(error, key) in errors"
+            :key="key"
+          >
+            {{ error }}
+          </li>
         </ul>
       </div>
-      <div v-if="!canEdit" class="ui message">
+      <div
+        v-if="!canEdit"
+        class="ui message"
+      >
         <translate translate-context="Content/Library/Paragraph">
           You don't have the permission to edit this object, but you can suggest changes. Once submitted, suggestions will be reviewed before approval.
         </translate>
       </div>
-      <div v-if="values" v-for="fieldConfig in config.fields" :key="fieldConfig.id" class="ui field">
+      <div
+        v-for="fieldConfig in config.fields"
+        v-if="values"
+        :key="fieldConfig.id"
+        class="ui field"
+      >
         <template v-if="fieldConfig.type === 'text'">
           <label :for="fieldConfig.id">{{ fieldConfig.label }}</label>
-          <input :type="fieldConfig.inputType || 'text'" v-model="values[fieldConfig.id]" :required="fieldConfig.required" :name="fieldConfig.id" :id="fieldConfig.id">
+          <input
+            :id="fieldConfig.id"
+            v-model="values[fieldConfig.id]"
+            :type="fieldConfig.inputType || 'text'"
+            :required="fieldConfig.required"
+            :name="fieldConfig.id"
+          >
         </template>
         <template v-else-if="fieldConfig.type === 'license'">
           <label :for="fieldConfig.id">{{ fieldConfig.label }}</label>
 
           <select
+            :id="fieldConfig.id"
             ref="license"
             v-model="values[fieldConfig.id]"
             :required="fieldConfig.required"
-            :id="fieldConfig.id"
-            class="ui fluid search dropdown">
-              <option :value="null"><translate translate-context="*/*/*">N/A</translate></option>
-              <option v-for="license in licenses" :key="license.code" :value="license.code">{{ license.name}}</option>
+            class="ui fluid search dropdown"
+          >
+            <option :value="null">
+              <translate translate-context="*/*/*">
+                N/A
+              </translate>
+            </option>
+            <option
+              v-for="license in licenses"
+              :key="license.code"
+              :value="license.code"
+            >
+              {{ license.name }}
+            </option>
           </select>
-          <button class="ui tiny basic left floated button" form="noop" @click.prevent="values[fieldConfig.id] = null">
-            <i class="x icon"></i>
-            <translate translate-context="Content/Library/Button.Label">Clear</translate>
+          <button
+            class="ui tiny basic left floated button"
+            form="noop"
+            @click.prevent="values[fieldConfig.id] = null"
+          >
+            <i class="x icon" />
+            <translate translate-context="Content/Library/Button.Label">
+              Clear
+            </translate>
           </button>
-
         </template>
         <template v-else-if="fieldConfig.type === 'content'">
           <label :for="fieldConfig.id">{{ fieldConfig.label }}</label>
-          <content-form v-model="values[fieldConfig.id].text" :field-id="fieldConfig.id" :rows="3"></content-form>
+          <content-form
+            v-model="values[fieldConfig.id].text"
+            :field-id="fieldConfig.id"
+            :rows="3"
+          />
         </template>
         <template v-else-if="fieldConfig.type === 'attachment'">
           <attachment-input
+            :id="fieldConfig.id"
             v-model="values[fieldConfig.id]"
             :initial-value="initialValues[fieldConfig.id]"
             :required="fieldConfig.required"
             :name="fieldConfig.id"
-            :id="fieldConfig.id"
-            @delete="values[fieldConfig.id] = initialValues[fieldConfig.id]">
+            @delete="values[fieldConfig.id] = initialValues[fieldConfig.id]"
+          >
             <span slot="label">{{ fieldConfig.label }}</span>
           </attachment-input>
-
         </template>
         <template v-else-if="fieldConfig.type === 'tags'">
           <label :for="fieldConfig.id">{{ fieldConfig.label }}</label>
           <tags-selector
+            :id="fieldConfig.id"
             ref="tags"
             v-model="values[fieldConfig.id]"
-            :id="fieldConfig.id"
-            required="fieldConfig.required"></tags-selector>
-          <button class="ui tiny basic left floated button" form="noop" @click.prevent="values[fieldConfig.id] = []">
-            <i class="x icon"></i>
-            <translate translate-context="Content/Library/Button.Label">Clear</translate>
+            required="fieldConfig.required"
+          />
+          <button
+            class="ui tiny basic left floated button"
+            form="noop"
+            @click.prevent="values[fieldConfig.id] = []"
+          >
+            <i class="x icon" />
+            <translate translate-context="Content/Library/Button.Label">
+              Clear
+            </translate>
           </button>
         </template>
         <div v-if="!lodash.isEqual(values[fieldConfig.id], initialValues[fieldConfig.id])">
-          <button class="ui tiny basic right floated reset button" form="noop" @click.prevent="values[fieldConfig.id] = lodash.clone(initialValues[fieldConfig.id])">
-            <i class="undo icon"></i>
-            <translate translate-context="Content/Library/Button.Label">Reset to initial value</translate>
+          <button
+            class="ui tiny basic right floated reset button"
+            form="noop"
+            @click.prevent="values[fieldConfig.id] = lodash.clone(initialValues[fieldConfig.id])"
+          >
+            <i class="undo icon" />
+            <translate translate-context="Content/Library/Button.Label">
+              Reset to initial value
+            </translate>
           </button>
         </div>
       </div>
       <div class="field">
         <label for="summary"><translate translate-context="*/*/*">Summary (optional)</translate></label>
-        <textarea name="change-summary" v-model="summary" id="change-summary" rows="3" :placeholder="labels.summaryPlaceholder"></textarea>
+        <textarea
+          id="change-summary"
+          v-model="summary"
+          name="change-summary"
+          rows="3"
+          :placeholder="labels.summaryPlaceholder"
+        />
       </div>
       <router-link
-        class="ui left floated button"
         v-if="objectType === 'track'"
+        class="ui left floated button"
         :to="{name: 'library.tracks.detail', params: {id: object.id }}"
       >
-        <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+        <translate translate-context="*/*/Button.Label/Verb">
+          Cancel
+        </translate>
       </router-link>
-      <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']" type="submit" :disabled="isLoading || !mutationPayload">
-        <translate v-if="canEdit" key="1" translate-context="Content/Library/Button.Label/Verb">Submit and apply edit</translate>
-        <translate v-else key="2" translate-context="Content/Library/Button.Label/Verb">Submit suggestion</translate>
+      <button
+        :class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
+        type="submit"
+        :disabled="isLoading || !mutationPayload"
+      >
+        <translate
+          v-if="canEdit"
+          key="1"
+          translate-context="Content/Library/Button.Label/Verb"
+        >
+          Submit and apply edit
+        </translate>
+        <translate
+          v-else
+          key="2"
+          translate-context="Content/Library/Button.Label/Verb"
+        >
+          Submit suggestion
+        </translate>
       </button>
     </form>
   </div>
@@ -134,24 +241,26 @@
 <script>
 import $ from 'jquery'
 import _ from '@/lodash'
-import axios from "axios"
+import axios from 'axios'
 import AttachmentInput from '@/components/common/AttachmentInput'
 import EditList from '@/components/library/EditList'
 import EditCard from '@/components/library/EditCard'
 import TagsSelector from '@/components/library/TagsSelector'
 import edits from '@/edits'
 
-import lodash from '@/lodash'
-
 export default {
-  props: ["objectType", "object", "licenses"],
   components: {
     EditList,
     EditCard,
     TagsSelector,
     AttachmentInput
   },
-  data() {
+  props: {
+    objectType: { type: String, required: true },
+    object: { type: Object, required: true },
+    licenses: { type: Array, required: true }
+  },
+  data () {
     return {
       isLoading: false,
       errors: [],
@@ -159,16 +268,9 @@ export default {
       initialValues: {},
       summary: '',
       submittedMutation: null,
-      showPendingReview: true,
-      lodash,
+      showPendingReview: true
     }
   },
-  created () {
-    this.setValues()
-  },
-  mounted() {
-    $(".ui.dropdown").dropdown({fullTextSearch: true})
-  },
   computed: {
     configs: edits.getConfigs,
     config: edits.getConfig,
@@ -176,7 +278,7 @@ export default {
     canEdit: edits.getCanEdit,
     labels () {
       return {
-        summaryPlaceholder: this.$pgettext('*/*/Placeholder', 'A short summary describing your changes.'),
+        summaryPlaceholder: this.$pgettext('*/*/Placeholder', 'A short summary describing your changes.')
       }
     },
     mutationsUrl () {
@@ -189,19 +291,20 @@ export default {
       if (this.objectType === 'artist') {
         return `artists/${this.object.id}/mutations/`
       }
+      return null
     },
     mutationPayload () {
-      let self = this
-      let changedFields = this.config.fields.filter(f => {
-        return !lodash.isEqual(self.values[f.id], self.initialValues[f.id])
+      const self = this
+      const changedFields = this.config.fields.filter(f => {
+        return !_.isEqual(self.values[f.id], self.initialValues[f.id])
       })
       if (changedFields.length === 0) {
         return null
       }
-      let payload = {
+      const payload = {
         type: 'update',
         payload: {},
-        summary: this.summary,
+        summary: this.summary
       }
       changedFields.forEach((f) => {
         payload.payload[f.id] = self.values[f.id]
@@ -210,26 +313,41 @@ export default {
     },
     editListFilters () {
       if (this.showPendingReview) {
-        return {is_approved: 'null'}
+        return { is_approved: 'null' }
       } else {
         return {}
       }
-    },
+    }
+  },
+  watch: {
+    'values.license' (newValue) {
+      if (newValue === null) {
+        $(this.$refs.license).dropdown('clear')
+      } else {
+        $(this.$refs.license).dropdown('set selected', newValue)
+      }
+    }
+  },
+  created () {
+    this.setValues()
+  },
+  mounted () {
+    $('.ui.dropdown').dropdown({ fullTextSearch: true })
   },
 
   methods: {
     setValues () {
-      let self = this
+      const self = this
       this.config.fields.forEach(f => {
-        self.$set(self.values, f.id, lodash.clone(f.getValue(self.object)))
-        self.$set(self.initialValues, f.id, lodash.clone(self.values[f.id]))
+        self.$set(self.values, f.id, _.clone(f.getValue(self.object)))
+        self.$set(self.initialValues, f.id, _.clone(self.values[f.id]))
       })
     },
-    submit() {
-      let self = this
+    submit () {
+      const self = this
       self.isLoading = true
       self.errors = []
-      let payload = _.clone(this.mutationPayload || {})
+      const payload = _.clone(this.mutationPayload || {})
       if (this.canEdit) {
         payload.is_approved = true
       }
@@ -244,15 +362,6 @@ export default {
         }
       )
     }
-  },
-  watch: {
-    'values.license' (newValue) {
-      if (newValue === null) {
-        $(this.$refs.license).dropdown('clear')
-      } else {
-        $(this.$refs.license).dropdown('set selected', newValue)
-      }
-    }
   }
 }
 </script>
diff --git a/front/src/components/library/EditList.vue b/front/src/components/library/EditList.vue
index 2ff1fc72a..e3efd0be3 100644
--- a/front/src/components/library/EditList.vue
+++ b/front/src/components/library/EditList.vue
@@ -1,16 +1,43 @@
 <template>
   <div class="wrapper">
     <h3 class="ui header">
-      <slot name="title"></slot>
+      <slot name="title" />
     </h3>
-    <slot v-if="!isLoading && objects.length === 0" name="empty-state"></slot>
-    <button v-if="nextPage || previousPage" :disabled="!previousPage" @click="fetchData(previousPage)" :class="['ui', {disabled: !previousPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle left', 'icon']"></i></button>
-    <button v-if="nextPage || previousPage" :disabled="!nextPage" @click="fetchData(nextPage)" :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle right', 'icon']"></i></button>
-    <div class="ui hidden divider"></div>
-    <div v-if="isLoading" class="ui inverted active dimmer">
-      <div class="ui loader"></div>
+    <slot
+      v-if="!isLoading && objects.length === 0"
+      name="empty-state"
+    />
+    <button
+      v-if="nextPage || previousPage"
+      :disabled="!previousPage"
+      :class="['ui', {disabled: !previousPage}, 'circular', 'icon', 'basic', 'button']"
+      @click="fetchData(previousPage)"
+    >
+      <i :class="['ui', 'angle left', 'icon']" />
+    </button>
+    <button
+      v-if="nextPage || previousPage"
+      :disabled="!nextPage"
+      :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"
+      @click="fetchData(nextPage)"
+    >
+      <i :class="['ui', 'angle right', 'icon']" />
+    </button>
+    <div class="ui hidden divider" />
+    <div
+      v-if="isLoading"
+      class="ui inverted active dimmer"
+    >
+      <div class="ui loader" />
     </div>
-    <edit-card @updated="fetchData(url)" @deleted="fetchData(url)" v-for="obj in objects" :key="obj.uuid" :obj="obj" :current-state="currentState" />
+    <edit-card
+      v-for="obj in objects"
+      :key="obj.uuid"
+      :obj="obj"
+      :current-state="currentState"
+      @updated="fetchData(url)"
+      @deleted="fetchData(url)"
+    />
   </div>
 </template>
 
@@ -21,14 +48,14 @@ import axios from 'axios'
 import EditCard from '@/components/library/EditCard'
 
 export default {
-  props: {
-    url: {type: String, required: true},
-    filters: {type: Object, required: false, default: () => {return {}}},
-    currentState: {required: false},
-  },
   components: {
     EditCard
   },
+  props: {
+    url: { type: String, required: true },
+    filters: { type: Object, required: false, default: () => { return {} } },
+    currentState: { type: Object, required: false, default: () => { return { } } }
+  },
   data () {
     return {
       objects: [],
@@ -39,6 +66,14 @@ export default {
       nextPage: null
     }
   },
+  watch: {
+    filters: {
+      handler () {
+        this.fetchData(this.url)
+      },
+      deep: true
+    }
+  },
   created () {
     this.fetchData(this.url)
   },
@@ -48,10 +83,10 @@ export default {
         return
       }
       this.isLoading = true
-      let self = this
-      let params = _.clone(this.filters)
+      const self = this
+      const params = _.clone(this.filters)
       params.page_size = this.limit
-      axios.get(url, {params: params}).then((response) => {
+      axios.get(url, { params: params }).then((response) => {
         self.previousPage = response.data.previous
         self.nextPage = response.data.next
         self.isLoading = false
@@ -60,14 +95,6 @@ export default {
         self.isLoading = false
         self.errors = error.backendErrors
       })
-    },
-  },
-  watch: {
-    filters: {
-      handler () {
-        this.fetchData(this.url)
-      },
-      deep: true
     }
   }
 }
diff --git a/front/src/components/library/FileUpload.vue b/front/src/components/library/FileUpload.vue
index bba0ad7c8..ca171c65d 100644
--- a/front/src/components/library/FileUpload.vue
+++ b/front/src/components/library/FileUpload.vue
@@ -1,27 +1,53 @@
-  <template>
+<template>
   <div class="component-file-upload">
     <div class="ui top attached tabular menu">
-      <a href="" :class="['item', {active: currentTab === 'uploads'}]" @click.prevent="currentTab = 'uploads'">
+      <a
+        href=""
+        :class="['item', {active: currentTab === 'uploads'}]"
+        @click.prevent="currentTab = 'uploads'"
+      >
         <translate translate-context="Content/Library/Tab.Title/Short">Uploading</translate>
-        <div v-if="files.length === 0" class="ui label">
+        <div
+          v-if="files.length === 0"
+          class="ui label"
+        >
           0
         </div>
-        <div v-else-if="files.length > uploadedFilesCount + erroredFilesCount" class="ui warning label">
+        <div
+          v-else-if="files.length > uploadedFilesCount + erroredFilesCount"
+          class="ui warning label"
+        >
           {{ uploadedFilesCount + erroredFilesCount }}/{{ files.length }}
         </div>
-        <div v-else :class="['ui', {'success': erroredFilesCount === 0}, {'danger': erroredFilesCount > 0}, 'label']">
+        <div
+          v-else
+          :class="['ui', {'success': erroredFilesCount === 0}, {'danger': erroredFilesCount > 0}, 'label']"
+        >
           {{ uploadedFilesCount + erroredFilesCount }}/{{ files.length }}
         </div>
       </a>
-      <a href="" :class="['item', {active: currentTab === 'processing'}]" @click.prevent="currentTab = 'processing'">
+      <a
+        href=""
+        :class="['item', {active: currentTab === 'processing'}]"
+        @click.prevent="currentTab = 'processing'"
+      >
         <translate translate-context="Content/Library/Tab.Title/Short">Processing</translate>
-        <div v-if="processableFiles === 0" class="ui label">
+        <div
+          v-if="processableFiles === 0"
+          class="ui label"
+        >
           0
         </div>
-        <div v-else-if="processableFiles > processedFilesCount" class="ui warning label">
+        <div
+          v-else-if="processableFiles > processedFilesCount"
+          class="ui warning label"
+        >
           {{ processedFilesCount }}/{{ processableFiles }}
         </div>
-        <div v-else :class="['ui', {'success': uploads.errored === 0}, {'danger': uploads.errored > 0}, 'label']">
+        <div
+          v-else
+          :class="['ui', {'success': uploads.errored === 0}, {'danger': uploads.errored > 0}, 'label']"
+        >
           {{ processedFilesCount }}/{{ processableFiles }}
         </div>
       </a>
@@ -30,177 +56,288 @@
       <div :class="['ui', {loading: isLoadingQuota}, 'container']">
         <div :class="['ui', {red: remainingSpace === 0}, {warning: remainingSpace > 0 && remainingSpace <= 50}, 'small', 'statistic']">
           <div class="label">
-            <translate translate-context="Content/Library/Paragraph">Remaining storage space</translate>
+            <translate translate-context="Content/Library/Paragraph">
+              Remaining storage space
+            </translate>
           </div>
           <div class="value">
-            {{ remainingSpace * 1000 * 1000 | humanSize}}
+            {{ remainingSpace * 1000 * 1000 | humanSize }}
           </div>
         </div>
-        <div class="ui divider"></div>
-        <h2 class="ui header"><translate translate-context="Content/Library/Title/Verb">Upload music from your local storage</translate></h2>
+        <div class="ui divider" />
+        <h2 class="ui header">
+          <translate translate-context="Content/Library/Title/Verb">
+            Upload music from your local storage
+          </translate>
+        </h2>
         <div class="ui message">
-          <p><translate translate-context="Content/Library/Paragraph">You are about to upload music to your library. Before proceeding, please ensure that:</translate></p>
+          <p>
+            <translate translate-context="Content/Library/Paragraph">
+              You are about to upload music to your library. Before proceeding, please ensure that:
+            </translate>
+          </p>
           <ul>
             <li v-if="library.privacy_level != 'me'">
-              <translate translate-context="Content/Library/List item">You are not uploading copyrighted content in a public library, otherwise you may be infringing the law</translate>
+              <translate translate-context="Content/Library/List item">
+                You are not uploading copyrighted content in a public library, otherwise you may be infringing the law
+              </translate>
             </li>
             <li>
-              <translate translate-context="Content/Library/List item">The music files you are uploading are tagged properly.</translate>&nbsp;
-              <a href="http://picard.musicbrainz.org/" target='_blank'><translate translate-context="Content/Library/Link">We recommend using Picard for that purpose.</translate></a>
+              <translate translate-context="Content/Library/List item">
+                The music files you are uploading are tagged properly.
+              </translate>&nbsp;
+              <a
+                href="http://picard.musicbrainz.org/"
+                target="_blank"
+              ><translate translate-context="Content/Library/Link">We recommend using Picard for that purpose.</translate></a>
             </li>
             <li>
-              <translate translate-context="Content/Library/List item">The music files you are uploading are in OGG, Flac, MP3 or AIFF format</translate>
+              <translate translate-context="Content/Library/List item">
+                The music files you are uploading are in OGG, Flac, MP3 or AIFF format
+              </translate>
             </li>
           </ul>
         </div>
         <file-upload-widget
+          ref="upload"
+          v-model="files"
           :class="['ui', 'icon', 'basic', 'button']"
           :post-action="uploadUrl"
           :multiple="true"
           :data="uploadData"
           :drop="true"
           :extensions="supportedExtensions"
-          v-model="files"
           name="audio_file"
           :thread="1"
           @input-file="inputFile"
-          ref="upload">
-          <i class="upload icon"></i>&nbsp;
-          <translate translate-context="Content/Library/Paragraph/Call to action">Click to select files to upload or drag and drop files or directories</translate>
-          <br />
-          <br />
-          <i><translate translate-context="Content/Library/Paragraph" :translate-params="{extensions: supportedExtensions.join(', ')}">Supported extensions: %{ extensions }</translate></i>
+        >
+          <i class="upload icon" />&nbsp;
+          <translate translate-context="Content/Library/Paragraph/Call to action">
+            Click to select files to upload or drag and drop files or directories
+          </translate>
+          <br>
+          <br>
+          <i><translate
+            translate-context="Content/Library/Paragraph"
+            :translate-params="{extensions: supportedExtensions.join(', ')}"
+          >Supported extensions: %{ extensions }</translate></i>
         </file-upload-widget>
       </div>
-      <div v-if="files.length > 0" class="table-wrapper">
-        <div class="ui hidden divider"></div>
+      <div
+        v-if="files.length > 0"
+        class="table-wrapper"
+      >
+        <div class="ui hidden divider" />
         <table class="ui unstackable table">
           <thead>
             <tr>
-              <th class="ten wide"><translate translate-context="Content/Library/Table.Label">Filename</translate></th>
-              <th><translate translate-context="Content/*/*/Noun">Size</translate></th>
-              <th><translate translate-context="*/*/*">Status</translate></th>
-              <th><translate translate-context="*/*/*">Actions</translate></th>
+              <th class="ten wide">
+                <translate translate-context="Content/Library/Table.Label">
+                  Filename
+                </translate>
+              </th>
+              <th>
+                <translate translate-context="Content/*/*/Noun">
+                  Size
+                </translate>
+              </th>
+              <th>
+                <translate translate-context="*/*/*">
+                  Status
+                </translate>
+              </th>
+              <th>
+                <translate translate-context="*/*/*">
+                  Actions
+                </translate>
+              </th>
             </tr>
             <tr v-if="retryableFiles.length > 1">
-              <th class="ten wide"></th>
-              <th></th>
-              <th></th>
+              <th class="ten wide" />
+              <th />
+              <th />
               <th>
-                <button class="ui right floated small basic button" @click.prevent="retry(retryableFiles)">
-                  <translate translate-context="Content/Library/Table">Retry failed uploads</translate>
+                <button
+                  class="ui right floated small basic button"
+                  @click.prevent="retry(retryableFiles)"
+                >
+                  <translate translate-context="Content/Library/Table">
+                    Retry failed uploads
+                  </translate>
                 </button>
               </th>
             </tr>
           </thead>
           <tbody>
-            <tr v-for="(file, index) in sortedFiles" :key="file.id">
-              <td :title="file.name">{{ file.name | truncate(60) }}</td>
+            <tr
+              v-for="file in sortedFiles"
+              :key="file.id"
+            >
+              <td :title="file.name">
+                {{ file.name | truncate(60) }}
+              </td>
               <td>{{ file.size | humanSize }}</td>
               <td>
-                <span v-if="file.error" class="ui tooltip" :data-tooltip="labels.tooltips[file.error]">
+                <span
+                  v-if="file.error"
+                  class="ui tooltip"
+                  :data-tooltip="labels.tooltips[file.error]"
+                >
                   <span class="ui danger icon label">
                     <i class="question circle outline icon" /> {{ file.error }}
                   </span>
                 </span>
-                <span v-else-if="file.success" class="ui success label">
-                  <translate translate-context="Content/Library/Table" key="1">Uploaded</translate>
+                <span
+                  v-else-if="file.success"
+                  class="ui success label"
+                >
+                  <translate
+                    key="1"
+                    translate-context="Content/Library/Table"
+                  >Uploaded</translate>
                 </span>
-                <span v-else-if="file.active" class="ui warning label">
-                  <translate translate-context="Content/Library/Table" key="2">Uploading…</translate>
+                <span
+                  v-else-if="file.active"
+                  class="ui warning label"
+                >
+                  <translate
+                    key="2"
+                    translate-context="Content/Library/Table"
+                  >Uploading…</translate>
                   ({{ parseInt(file.progress) }}%)
                 </span>
-                <span v-else class="ui label"><translate translate-context="Content/Library/*/Short" key="3">Pending</translate></span>
+                <span
+                  v-else
+                  class="ui label"
+                ><translate
+                  key="3"
+                  translate-context="Content/Library/*/Short"
+                >Pending</translate></span>
               </td>
               <td>
                 <template v-if="file.error">
                   <button
+                    v-if="retryableFiles.indexOf(file) > -1"
                     class="ui tiny basic icon right floated button"
                     :title="labels.retry"
                     @click.prevent="retry([file])"
-                    v-if="retryableFiles.indexOf(file) > -1">
-                    <i class="redo icon"></i>
+                  >
+                    <i class="redo icon" />
                   </button>
                 </template>
                 <template v-else-if="!file.success">
-                  <button class="ui tiny basic danger icon right floated button" @click.prevent="$refs.upload.remove(file)"><i class="delete icon"></i></button>
+                  <button
+                    class="ui tiny basic danger icon right floated button"
+                    @click.prevent="$refs.upload.remove(file)"
+                  >
+                    <i class="delete icon" />
+                  </button>
                 </template>
               </td>
             </tr>
           </tbody>
         </table>
       </div>
-      <div class="ui divider"></div>
-      <h2 class="ui header"><translate translate-context="Content/Library/Title/Verb">Import music from your server</translate></h2>
-      <div v-if="fsErrors.length > 0" role="alert" class="ui negative message">
-        <h3 class="header"><translate translate-context="Content/*/Error message.Title">Error while launching import</translate></h3>
+      <div class="ui divider" />
+      <h2 class="ui header">
+        <translate translate-context="Content/Library/Title/Verb">
+          Import music from your server
+        </translate>
+      </h2>
+      <div
+        v-if="fsErrors.length > 0"
+        role="alert"
+        class="ui negative message"
+      >
+        <h3 class="header">
+          <translate translate-context="Content/*/Error message.Title">
+            Error while launching import
+          </translate>
+        </h3>
         <ul class="list">
-          <li v-for="error in fsErrors">{{ error }}</li>
+          <li
+            v-for="(error, key) in fsErrors"
+            :key="key"
+          >
+            {{ error }}
+          </li>
         </ul>
       </div>
       <fs-browser
         v-model="fsPath"
-        @import="importFs"
         :loading="isLoadingFs"
-        :data="fsStatus"></fs-browser>
+        :data="fsStatus"
+        @import="importFs"
+      />
       <template v-if="fsStatus && fsStatus.import">
-        <h3 class="ui header"><translate translate-context="Content/Library/Title/Verb">Import status</translate></h3>
+        <h3 class="ui header">
+          <translate translate-context="Content/Library/Title/Verb">
+            Import status
+          </translate>
+        </h3>
         <p v-if="fsStatus.import.reference != importReference">
-          <translate translate-context="Content/Library/Paragraph">Results of your previous import:</translate>
+          <translate translate-context="Content/Library/Paragraph">
+            Results of your previous import:
+          </translate>
         </p>
         <p v-else>
-          <translate translate-context="Content/Library/Paragraph">Results of your import:</translate>
+          <translate translate-context="Content/Library/Paragraph">
+            Results of your import:
+          </translate>
         </p>
 
         <button
+          v-if="fsStatus.import.status === 'started' || fsStatus.import.status === 'pending'"
           class="ui button"
           @click="cancelFsScan"
-          v-if="fsStatus.import.status === 'started' || fsStatus.import.status === 'pending'">
-          <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+        >
+          <translate translate-context="*/*/Button.Label/Verb">
+            Cancel
+          </translate>
         </button>
-        <fs-logs :data="fsStatus.import"></fs-logs>
+        <fs-logs :data="fsStatus.import" />
       </template>
-
-
     </div>
     <div :class="['ui', 'bottom', 'attached', 'segment', {hidden: currentTab != 'processing'}]">
       <library-files-table
         :needs-refresh="needsRefresh"
         ordering-config-name="library.detail.upload"
-        @fetch-start="needsRefresh = false"
         :filters="{import_reference: importReference}"
-        :custom-objects="Object.values(uploads.objects)"></library-files-table>
+        :custom-objects="Object.values(uploads.objects)"
+        @fetch-start="needsRefresh = false"
+      />
     </div>
   </div>
 </template>
 
 <script>
-import _ from "@/lodash"
-import $ from "jquery";
-import axios from "axios";
-import logger from "@/logging";
-import FileUploadWidget from "./FileUploadWidget";
-import FsBrowser from "./FsBrowser";
-import FsLogs from "./FsLogs";
-import LibraryFilesTable from "@/views/content/libraries/FilesTable";
-import moment from "moment";
+import _ from '@/lodash'
+import axios from 'axios'
+import FileUploadWidget from './FileUploadWidget'
+import FsBrowser from './FsBrowser'
+import FsLogs from './FsLogs'
+import LibraryFilesTable from '@/views/content/libraries/FilesTable'
+import moment from 'moment'
 
 export default {
-  props: ["library", "defaultImportReference"],
   components: {
     FileUploadWidget,
     LibraryFilesTable,
     FsBrowser,
-    FsLogs,
+    FsLogs
+  },
+  props: {
+    library: { type: Object, required: true },
+    defaultImportReference: { type: String, required: false, default: '' }
   },
-  data() {
-    let importReference = this.defaultImportReference || moment().format();
-    this.$router.replace({ query: { import: importReference } });
+  data () {
+    const importReference = this.defaultImportReference || moment().format()
+    this.$router.replace({ query: { import: importReference } })
     return {
       files: [],
       needsRefresh: false,
-      currentTab: "uploads",
-      uploadUrl: this.$store.getters['instance/absoluteUrl']("/api/v1/uploads/"),
+      currentTab: 'uploads',
+      uploadUrl: this.$store.getters['instance/absoluteUrl']('/api/v1/uploads/'),
       importReference,
       isLoadingQuota: false,
       quotaStatus: null,
@@ -217,202 +354,80 @@ export default {
       isLoadingFs: false,
       fsInterval: null,
       fsErrors: []
-    };
-  },
-  created() {
-    this.fetchStatus();
-    if (this.$store.state.auth.availablePermissions['library']) {
-      this.fetchFs(true)
-      this.fsInterval = setInterval(() => {
-        this.fetchFs(false)
-      }, 5000);
-    }
-    this.fetchQuota();
-    this.$store.commit("ui/addWebsocketEventHandler", {
-      eventName: "import.status_updated",
-      id: "fileUpload",
-      handler: this.handleImportEvent
-    });
-    window.onbeforeunload = e => this.onBeforeUnload(e);
-  },
-  destroyed() {
-    this.$store.commit("ui/removeWebsocketEventHandler", {
-      eventName: "import.status_updated",
-      id: "fileUpload"
-    });
-    window.onbeforeunload = null;
-    if (this.fsInterval) {
-      clearInterval(this.fsInterval)
-    }
-  },
-  methods: {
-    onBeforeUnload(e = {}) {
-      const returnValue = ('This page is asking you to confirm that you want to leave - data you have entered may not be saved.');
-      if (!this.hasActiveUploads) return null;
-      Object.assign(e, {
-        returnValue,
-      });
-      return returnValue;
-    },
-    fetchQuota () {
-      let self = this
-      self.isLoadingQuota = true
-      axios.get('users/me/').then((response) => {
-        self.quotaStatus = response.data.quota_status
-        self.isLoadingQuota = false
-      })
-    },
-    fetchFs (updateLoading) {
-      let self = this
-      if (updateLoading) {
-        self.isLoadingFs = true
-      }
-      axios.get('libraries/fs-import', {params: {path: this.fsPath.join('/')}}).then((response) => {
-        self.fsStatus = response.data
-        if (updateLoading) {
-          self.isLoadingFs = false
-        }
-      })
-    },
-    importFs () {
-      let self = this
-      self.isLoadingFs = true
-      let payload = {
-        path: this.fsPath.join('/'),
-        library: this.library.uuid,
-        import_reference: this.importReference,
-      }
-      axios.post('libraries/fs-import', payload).then((response) => {
-        self.fsStatus = response.data
-        self.isLoadingFs = false
-      }, error => {
-        self.isLoadingFs = false
-        self.fsErrors = error.backendErrors
-      })
-    },
-    async cancelFsScan () {
-      await axios.delete('libraries/fs-import')
-      this.fetchFs()
-    },
-    inputFile(newFile, oldFile) {
-      if (!newFile) {
-        return
-      }
-      if (this.remainingSpace < newFile.size / (1000 * 1000)) {
-        newFile.error = 'denied'
-      } else {
-        this.$refs.upload.active = true;
-      }
-    },
-    fetchStatus() {
-      let self = this;
-      let statuses = ["pending", "errored", "skipped", "finished"];
-      statuses.forEach(status => {
-        axios
-          .get("uploads/", {
-            params: {
-              import_reference: self.importReference,
-              import_status: status,
-              page_size: 1
-            }
-          })
-          .then(response => {
-            self.uploads[status] = response.data.count;
-          });
-      });
-    },
-    handleImportEvent(event) {
-      let self = this;
-      if (event.upload.import_reference != self.importReference) {
-        return;
-      }
-      this.$nextTick(() => {
-        self.uploads[event.old_status] -= 1;
-        self.uploads[event.new_status] += 1;
-        self.uploads.objects[event.upload.uuid] = event.upload;
-        self.needsRefresh = true
-      });
-    },
-    retry (files) {
-      files.forEach((file) => {
-        this.$refs.upload.update(file, {error: '', progress: '0.00'})
-      })
-      this.$refs.upload.active = true;
-
     }
   },
   computed: {
     supportedExtensions () {
       return this.$store.state.ui.supportedExtensions
     },
-    labels() {
-      let denied = this.$pgettext('Content/Library/Help text',
-        "Upload denied, ensure the file is not too big and that you have not reached your quota"
-      );
-      let server = this.$pgettext('Content/Library/Help text',
-        "Cannot upload this file, ensure it is not too big"
-      );
-      let network = this.$pgettext('Content/Library/Help text',
-        "A network error occurred while uploading this file"
-      );
-      let timeout = this.$pgettext('Content/Library/Help text', "Upload timeout, please try again");
-      let extension = this.$pgettext('Content/Library/Help text',
-        "Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }"
-      );
+    labels () {
+      const denied = this.$pgettext('Content/Library/Help text',
+        'Upload denied, ensure the file is not too big and that you have not reached your quota'
+      )
+      const server = this.$pgettext('Content/Library/Help text',
+        'Cannot upload this file, ensure it is not too big'
+      )
+      const network = this.$pgettext('Content/Library/Help text',
+        'A network error occurred while uploading this file'
+      )
+      const timeout = this.$pgettext('Content/Library/Help text', 'Upload timeout, please try again')
+      const extension = this.$pgettext('Content/Library/Help text',
+        'Invalid file type, ensure you are uploading an audio file. Supported file extensions are %{ extensions }'
+      )
       return {
         tooltips: {
           denied,
           server,
           network,
           timeout,
-          retry: this.$pgettext('*/*/*/Verb', "Retry"),
+          retry: this.$pgettext('*/*/*/Verb', 'Retry'),
           extension: this.$gettextInterpolate(extension, {
-            extensions: this.supportedExtensions.join(", ")
+            extensions: this.supportedExtensions.join(', ')
           })
         }
-      };
+      }
     },
-    uploadedFilesCount() {
+    uploadedFilesCount () {
       return this.files.filter(f => {
-        return f.success;
-      }).length;
+        return f.success
+      }).length
     },
-    uploadingFilesCount() {
+    uploadingFilesCount () {
       return this.files.filter(f => {
-        return !f.success && !f.error;
-      }).length;
+        return !f.success && !f.error
+      }).length
     },
-    erroredFilesCount() {
+    erroredFilesCount () {
       return this.files.filter(f => {
-        return f.error;
-      }).length;
+        return f.error
+      }).length
     },
     retryableFiles () {
       return this.files.filter(f => {
-        return f.error;
-      });
+        return f.error
+      })
     },
-    processableFiles() {
+    processableFiles () {
       return (
         this.uploads.pending +
         this.uploads.skipped +
         this.uploads.errored +
         this.uploads.finished +
         this.uploadedFilesCount
-      );
+      )
     },
-    processedFilesCount() {
+    processedFilesCount () {
       return (
         this.uploads.skipped + this.uploads.errored + this.uploads.finished
-      );
+      )
     },
-    uploadData: function() {
+    uploadData: function () {
       return {
         library: this.library.uuid,
         import_reference: this.importReference
-      };
+      }
     },
-    sortedFiles() {
+    sortedFiles () {
       // return errored files on top
 
       return _.sortBy(this.files.map(f => {
@@ -447,12 +462,12 @@ export default {
     }
   },
   watch: {
-    importReference: _.debounce(function() {
-      this.$router.replace({ query: { import: this.importReference } });
+    importReference: _.debounce(function () {
+      this.$router.replace({ query: { import: this.importReference } })
     }, 500),
     remainingSpace (newValue) {
       if (newValue <= 0) {
-        this.$refs.upload.active = false;
+        this.$refs.upload.active = false
       }
     },
     'uploads.finished' (v, o) {
@@ -460,9 +475,130 @@ export default {
         this.$emit('uploads-finished', v - o)
       }
     },
-    "fsPath" () {
+    'fsPath' () {
+      this.fetchFs(true)
+    }
+  },
+  created () {
+    this.fetchStatus()
+    if (this.$store.state.auth.availablePermissions.library) {
       this.fetchFs(true)
+      this.fsInterval = setInterval(() => {
+        this.fetchFs(false)
+      }, 5000)
+    }
+    this.fetchQuota()
+    this.$store.commit('ui/addWebsocketEventHandler', {
+      eventName: 'import.status_updated',
+      id: 'fileUpload',
+      handler: this.handleImportEvent
+    })
+    window.onbeforeunload = e => this.onBeforeUnload(e)
+  },
+  destroyed () {
+    this.$store.commit('ui/removeWebsocketEventHandler', {
+      eventName: 'import.status_updated',
+      id: 'fileUpload'
+    })
+    window.onbeforeunload = null
+    if (this.fsInterval) {
+      clearInterval(this.fsInterval)
+    }
+  },
+  methods: {
+    onBeforeUnload (e = {}) {
+      const returnValue = ('This page is asking you to confirm that you want to leave - data you have entered may not be saved.')
+      if (!this.hasActiveUploads) return null
+      Object.assign(e, {
+        returnValue
+      })
+      return returnValue
+    },
+    fetchQuota () {
+      const self = this
+      self.isLoadingQuota = true
+      axios.get('users/me/').then((response) => {
+        self.quotaStatus = response.data.quota_status
+        self.isLoadingQuota = false
+      })
+    },
+    fetchFs (updateLoading) {
+      const self = this
+      if (updateLoading) {
+        self.isLoadingFs = true
+      }
+      axios.get('libraries/fs-import', { params: { path: this.fsPath.join('/') } }).then((response) => {
+        self.fsStatus = response.data
+        if (updateLoading) {
+          self.isLoadingFs = false
+        }
+      })
+    },
+    importFs () {
+      const self = this
+      self.isLoadingFs = true
+      const payload = {
+        path: this.fsPath.join('/'),
+        library: this.library.uuid,
+        import_reference: this.importReference
+      }
+      axios.post('libraries/fs-import', payload).then((response) => {
+        self.fsStatus = response.data
+        self.isLoadingFs = false
+      }, error => {
+        self.isLoadingFs = false
+        self.fsErrors = error.backendErrors
+      })
+    },
+    async cancelFsScan () {
+      await axios.delete('libraries/fs-import')
+      this.fetchFs()
+    },
+    inputFile (newFile, oldFile) {
+      if (!newFile) {
+        return
+      }
+      if (this.remainingSpace < newFile.size / (1000 * 1000)) {
+        newFile.error = 'denied'
+      } else {
+        this.$refs.upload.active = true
+      }
+    },
+    fetchStatus () {
+      const self = this
+      const statuses = ['pending', 'errored', 'skipped', 'finished']
+      statuses.forEach(status => {
+        axios
+          .get('uploads/', {
+            params: {
+              import_reference: self.importReference,
+              import_status: status,
+              page_size: 1
+            }
+          })
+          .then(response => {
+            self.uploads[status] = response.data.count
+          })
+      })
+    },
+    handleImportEvent (event) {
+      const self = this
+      if (event.upload.import_reference !== self.importReference) {
+        return
+      }
+      this.$nextTick(() => {
+        self.uploads[event.old_status] -= 1
+        self.uploads[event.new_status] += 1
+        self.uploads.objects[event.upload.uuid] = event.upload
+        self.needsRefresh = true
+      })
+    },
+    retry (files) {
+      files.forEach((file) => {
+        this.$refs.upload.update(file, { error: '', progress: '0.00' })
+      })
+      this.$refs.upload.active = true
     }
   }
-};
+}
 </script>
diff --git a/front/src/components/library/FileUploadWidget.vue b/front/src/components/library/FileUploadWidget.vue
index c91916c03..3f5077257 100644
--- a/front/src/components/library/FileUploadWidget.vue
+++ b/front/src/components/library/FileUploadWidget.vue
@@ -1,25 +1,25 @@
 <script>
 import FileUpload from 'vue-upload-component'
-import {setCsrf} from '@/utils'
+import { setCsrf } from '@/utils'
 
 export default {
   extends: FileUpload,
   methods: {
     uploadHtml5 (file) {
-      let form = new window.FormData()
-      let filename = file.file.filename || file.name
+      const form = new window.FormData()
+      const filename = file.file.filename || file.name
       let value
-      let data = {...file.data}
+      const data = { ...file.data }
       if (data.import_metadata) {
-        data.import_metadata = {...(data.import_metadata || {})}
+        data.import_metadata = { ...(data.import_metadata || {}) }
         if (data.channel && !data.import_metadata.title) {
-          data.import_metadata.title = filename.replace(/\.[^/.]+$/, "")
+          data.import_metadata.title = filename.replace(/\.[^/.]+$/, '')
         }
         data.import_metadata = JSON.stringify(data.import_metadata)
       }
-      for (let key in data) {
+      for (const key in data) {
         value = data[key]
-        if (value &&   typeof value === 'object' && typeof value.toString !== 'function') {
+        if (value && typeof value === 'object' && typeof value.toString !== 'function') {
           if (value instanceof File) {
             form.append(key, value, value.name)
           } else {
@@ -31,7 +31,7 @@ export default {
       }
       form.append('source', `upload://${filename}`)
       form.append(this.name, file.file, filename)
-      let xhr = new XMLHttpRequest()
+      const xhr = new XMLHttpRequest()
       xhr.open('POST', file.postAction)
       setCsrf(xhr)
       if (this.$store.state.auth.oauth.accessToken) {
diff --git a/front/src/components/library/FsBrowser.vue b/front/src/components/library/FsBrowser.vue
index 6140475b7..fced6bef6 100644
--- a/front/src/components/library/FsBrowser.vue
+++ b/front/src/components/library/FsBrowser.vue
@@ -1,9 +1,18 @@
 <template>
   <div :class="['ui', {loading}, 'segment']">
     <div class="ui fluid action input">
-      <input class="ui disabled" disabled :value="data.root + '/' + value.join('/')" />
-      <button class="ui button" @click.prevent="$emit('import')">
-        <translate translate-context="Content/Library/Button/Verb">Import</translate>
+      <input
+        class="ui disabled"
+        disabled
+        :value="data.root + '/' + value.join('/')"
+      >
+      <button
+        class="ui button"
+        @click.prevent="$emit('import')"
+      >
+        <translate translate-context="Content/Library/Button/Verb">
+          Import
+        </translate>
       </button>
     </div>
     <div class="ui list component-fs-browser">
@@ -12,20 +21,27 @@
         class="item"
         href=""
         @click.prevent="handleClick({name: '..', dir: true})"
-        >
-        <i class="folder icon"></i>
+      >
+        <i class="folder icon" />
         <div class="content">
           <div class="header">..</div>
         </div>
       </a>
       <a
+        v-for="e in data.content"
+        :key="e.name"
         class="item"
         href=""
         @click.prevent="handleClick(e)"
-        v-for="e in data.content"
-        :key="e.name">
-        <i class="folder icon" v-if="e.dir"></i>
-        <i class="file icon" v-else></i>
+      >
+        <i
+          v-if="e.dir"
+          class="folder icon"
+        />
+        <i
+          v-else
+          class="file icon"
+        />
         <div class="content">
           <div class="header">{{ e.name }}</div>
         </div>
@@ -35,14 +51,18 @@
 </template>
 <script>
 export default {
-  props: ["data", "loading", "value"],
+  props: {
+    data: { type: Object, required: true },
+    loading: { type: Boolean, required: true },
+    value: { type: String, required: true }
+  },
   methods: {
     handleClick (element) {
       if (!element.dir) {
         return
       }
-      if (element.name === "..") {
-        let newValue = [...this.value]
+      if (element.name === '..') {
+        const newValue = [...this.value]
         newValue.pop()
         this.$emit('input', newValue)
       } else {
@@ -51,4 +71,4 @@ export default {
     }
   }
 }
-</script>
\ No newline at end of file
+</script>
diff --git a/front/src/components/library/FsLogs.vue b/front/src/components/library/FsLogs.vue
index 8c26ca49e..518076708 100644
--- a/front/src/components/library/FsLogs.vue
+++ b/front/src/components/library/FsLogs.vue
@@ -1,17 +1,27 @@
 <template>
   <div class="ui segment component-fs-logs">
-    <div class="ui active dimmer" v-if="data.status === 'pending'">
+    <div
+      v-if="data.status === 'pending'"
+      class="ui active dimmer"
+    >
       <div class="ui text loader">
-        <translate translate-context="Content/Library/Paragraph">Import hasn't started yet</translate>
+        <translate translate-context="Content/Library/Paragraph">
+          Import hasn't started yet
+        </translate>
       </div>
     </div>
-    <template v-else v-for="(row, idx) in data.logs">
-      <p :key="idx">{{ row }}</p>
+    <template
+      v-for="(row, idx) in data.logs"
+      v-else
+    >
+      <p :key="idx">
+        {{ row }}
+      </p>
     </template>
   </div>
 </template>
 <script>
 export default {
-  props: ["data"],
+  props: { data: { type: Object, required: true } }
 }
-</script>
\ No newline at end of file
+</script>
diff --git a/front/src/components/library/Home.vue b/front/src/components/library/Home.vue
index e591d6ad4..e70eaebb8 100644
--- a/front/src/components/library/Home.vue
+++ b/front/src/components/library/Home.vue
@@ -1,97 +1,125 @@
 <template>
-  <main v-title="labels.title" :key="$router.currentRoute.name">
+  <main
+    :key="$router.currentRoute.name"
+    v-title="labels.title"
+  >
     <section class="ui vertical stripe segment">
       <div class="ui stackable three column grid">
         <div class="column">
-          <track-widget :url="'history/listenings/'" :filters="{scope: scope, ordering: '-creation_date'}">
-            <template slot="title"><translate translate-context="Content/Home/Title">Recently listened</translate></template>
+          <track-widget
+            :url="'history/listenings/'"
+            :filters="{scope: scope, ordering: '-creation_date'}"
+          >
+            <template slot="title">
+              <translate translate-context="Content/Home/Title">
+                Recently listened
+              </translate>
+            </template>
           </track-widget>
         </div>
         <div class="column">
-          <track-widget :url="'favorites/tracks/'" :filters="{scope: scope, ordering: '-creation_date'}">
-            <template slot="title"><translate translate-context="Content/Home/Title">Recently favorited</translate></template>
+          <track-widget
+            :url="'favorites/tracks/'"
+            :filters="{scope: scope, ordering: '-creation_date'}"
+          >
+            <template slot="title">
+              <translate translate-context="Content/Home/Title">
+                Recently favorited
+              </translate>
+            </template>
           </track-widget>
         </div>
         <div class="column">
-          <playlist-widget :url="'playlists/'" :filters="{scope: scope, playable: true, ordering: '-modification_date'}">
-            <template slot="title"><translate translate-context="*/*/*">Playlists</translate></template>
+          <playlist-widget
+            :url="'playlists/'"
+            :filters="{scope: scope, playable: true, ordering: '-modification_date'}"
+          >
+            <template slot="title">
+              <translate translate-context="*/*/*">
+                Playlists
+              </translate>
+            </template>
           </playlist-widget>
         </div>
       </div>
-      <div class="ui section hidden divider"></div>
+      <div class="ui section hidden divider" />
       <div class="ui stackable one column grid">
         <div class="column">
           <album-widget :filters="{scope: scope, playable: true, ordering: '-creation_date'}">
-            <template slot="title"><translate translate-context="Content/Home/Title">Recently added</translate></template>
+            <template slot="title">
+              <translate translate-context="Content/Home/Title">
+                Recently added
+              </translate>
+            </template>
           </album-widget>
         </div>
       </div>
       <template v-if="scope === 'all'">
-        <h3 class="ui header" >
-          <translate translate-context="*/*/*">New channels</translate>
+        <h3 class="ui header">
+          <translate translate-context="*/*/*">
+            New channels
+          </translate>
         </h3>
-        <channels-widget :show-modification-date="true" :limit="12" :filters="{ordering: '-creation_date', external: 'false'}"></channels-widget>
+        <channels-widget
+          :show-modification-date="true"
+          :limit="12"
+          :filters="{ordering: '-creation_date', external: 'false'}"
+        />
       </template>
-
-
     </section>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import Search from "@/components/audio/Search"
-import logger from "@/logging"
-import ChannelsWidget from "@/components/audio/ChannelsWidget"
-import ArtistCard from "@/components/audio/artist/Card"
-import TrackWidget from "@/components/audio/track/Widget"
-import AlbumWidget from "@/components/audio/album/Widget"
-import PlaylistWidget from "@/components/playlists/Widget"
+import axios from 'axios'
+import logger from '@/logging'
+import ChannelsWidget from '@/components/audio/ChannelsWidget'
+import TrackWidget from '@/components/audio/track/Widget'
+import AlbumWidget from '@/components/audio/album/Widget'
+import PlaylistWidget from '@/components/playlists/Widget'
 
-const ARTISTS_URL = "artists/"
+const ARTISTS_URL = 'artists/'
 
 export default {
-  name: "library",
-  props: {
-    scope: {default: 'all'}
-  },
+  name: 'Library',
   components: {
-    Search,
-    ArtistCard,
     TrackWidget,
     AlbumWidget,
     PlaylistWidget,
-    ChannelsWidget,
+    ChannelsWidget
+  },
+  props: {
+    scope: { type: String, default: 'all' }
   },
-  data() {
+  data () {
     return {
       artists: [],
-      isLoadingArtists: false,
+      isLoadingArtists: false
     }
   },
-  created() {
-    this.fetchArtists()
-  },
   computed: {
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext('Head/Home/Title', "Library")
+        title: this.$pgettext('Head/Home/Title', 'Library')
       }
     }
   },
+  created () {
+    this.fetchArtists()
+  },
   methods: {
-    fetchArtists() {
-      var self = this
+    fetchArtists () {
+      const self = this
       this.isLoadingArtists = true
-      let params = {
-        ordering: "-creation_date",
+      const params = {
+        ordering: '-creation_date',
         playable: true
       }
-      let url = ARTISTS_URL
-      logger.default.time("Loading latest artists")
+      const url = ARTISTS_URL
+      logger.default.time('Loading latest artists')
       axios.get(url, { params: params }).then(response => {
         self.artists = response.data.results
-        logger.default.timeEnd("Loading latest artists")
+        logger.default.timeEnd('Loading latest artists')
         self.isLoadingArtists = false
       })
     }
diff --git a/front/src/components/library/ImportStatusModal.vue b/front/src/components/library/ImportStatusModal.vue
index d228ef6c0..b20531f21 100644
--- a/front/src/components/library/ImportStatusModal.vue
+++ b/front/src/components/library/ImportStatusModal.vue
@@ -1,29 +1,56 @@
 <template>
-
   <modal :show.sync="showModal">
     <h4 class="header">
-      <translate translate-context="Popup/Import/Title">Import detail</translate>
+      <translate translate-context="Popup/Import/Title">
+        Import detail
+      </translate>
     </h4>
-    <div class="content" v-if="upload">
+    <div
+      v-if="upload"
+      class="content"
+    >
       <div class="description">
-        <div class="ui message" v-if="upload.import_status === 'pending'">
-          <translate translate-context="Popup/Import/Message">Upload is still pending and will soon be processed by the server.</translate>
+        <div
+          v-if="upload.import_status === 'pending'"
+          class="ui message"
+        >
+          <translate translate-context="Popup/Import/Message">
+            Upload is still pending and will soon be processed by the server.
+          </translate>
         </div>
-        <div class="ui success message" v-if="upload.import_status === 'finished'">
-          <translate translate-context="Popup/Import/Message">Upload was successfully processed by the server.</translate>
+        <div
+          v-if="upload.import_status === 'finished'"
+          class="ui success message"
+        >
+          <translate translate-context="Popup/Import/Message">
+            Upload was successfully processed by the server.
+          </translate>
         </div>
-        <div role="alert" class="ui warning message" v-if="upload.import_status === 'skipped'">
-          <translate translate-context="Popup/Import/Message">Upload was skipped because a similar one is already available in one of your libraries.</translate>
+        <div
+          v-if="upload.import_status === 'skipped'"
+          role="alert"
+          class="ui warning message"
+        >
+          <translate translate-context="Popup/Import/Message">
+            Upload was skipped because a similar one is already available in one of your libraries.
+          </translate>
         </div>
-        <div class="ui error message" v-if="upload.import_status === 'errored'">
-          <translate translate-context="Popup/Import/Message">An error occurred during upload processing. You will find more information below.</translate>
+        <div
+          v-if="upload.import_status === 'errored'"
+          class="ui error message"
+        >
+          <translate translate-context="Popup/Import/Message">
+            An error occurred during upload processing. You will find more information below.
+          </translate>
         </div>
         <template v-if="upload.import_status === 'errored'">
           <table class="ui very basic collapsing celled table">
             <tbody>
               <tr>
                 <td>
-                  <translate translate-context="Popup/Import/Table.Label/Noun">Error type</translate>
+                  <translate translate-context="Popup/Import/Table.Label/Noun">
+                    Error type
+                  </translate>
                 </td>
                 <td>
                   {{ getErrorData(upload).label }}
@@ -31,30 +58,43 @@
               </tr>
               <tr>
                 <td>
-                  <translate translate-context="Popup/Import/Table.Label/Noun">Error detail</translate>
+                  <translate translate-context="Popup/Import/Table.Label/Noun">
+                    Error detail
+                  </translate>
                 </td>
                 <td>
                   {{ getErrorData(upload).detail }}
                   <ul v-if="getErrorData(upload).errorRows.length > 0">
-                    <li v-for="row in getErrorData(upload).errorRows">
-                      {{ row.key}}: {{ row.value}}
+                    <li
+                      v-for="row in getErrorData(upload).errorRows"
+                      :key="row.key"
+                    >
+                      {{ row.key }}: {{ row.value }}
                     </li>
                   </ul>
                 </td>
               </tr>
               <tr>
                 <td>
-                  <translate translate-context="Footer/*/Link">Getting help</translate>
+                  <translate translate-context="Footer/*/Link">
+                    Getting help
+                  </translate>
                 </td>
                 <td>
                   <ul>
                     <li>
-                      <a :href="getErrorData(upload).documentationUrl" target="_blank">
+                      <a
+                        :href="getErrorData(upload).documentationUrl"
+                        target="_blank"
+                      >
                         <translate translate-context="Popup/Import/Table.Label/Value">Read our documentation for this error</translate>
                       </a>
                     </li>
                     <li>
-                      <a :href="getErrorData(upload).supportUrl" target="_blank">
+                      <a
+                        :href="getErrorData(upload).supportUrl"
+                        target="_blank"
+                      >
                         <translate translate-context="Popup/Import/Table.Label/Value">Open a support thread (include the debug information below in your message)</translate>
                       </a>
                     </li>
@@ -63,11 +103,17 @@
               </tr>
               <tr>
                 <td>
-                  <translate translate-context="Popup/Import/Table.Label/Noun">Debug information</translate>
+                  <translate translate-context="Popup/Import/Table.Label/Noun">
+                    Debug information
+                  </translate>
                 </td>
                 <td>
                   <div class="ui form">
-                    <textarea class="ui textarea" rows="10" :value="getErrorData(upload).debugInfo"></textarea>
+                    <textarea
+                      class="ui textarea"
+                      rows="10"
+                      :value="getErrorData(upload).debugInfo"
+                    />
                   </div>
                 </td>
               </tr>
@@ -78,7 +124,9 @@
     </div>
     <div class="actions">
       <button class="ui deny button">
-        <translate translate-context="*/*/Button.Label/Verb">Close</translate>
+        <translate translate-context="*/*/Button.Label/Verb">
+          Close
+        </translate>
       </button>
     </div>
   </modal>
@@ -86,11 +134,11 @@
 <script>
 import Modal from '@/components/semantic/Modal'
 
-function getErrors(payload) {
-  let errors = []
-  for (var k in payload) {
-    if (payload.hasOwnProperty(k)) {
-      let value = payload[k]
+function getErrors (payload) {
+  const errors = []
+  for (const k in payload) {
+    if (Object.prototype.hasOwnProperty.call(payload, k)) {
+      const value = payload[k]
       if (Array.isArray(value)) {
         errors.push({
           key: k,
@@ -113,19 +161,30 @@ function getErrors(payload) {
 }
 
 export default {
-  props: ['upload', "show"],
   components: {
     Modal
   },
+  props: {
+    upload: { type: Object, required: true },
+    show: { type: Boolean }
+  },
   data () {
     return {
       showModal: this.show
     }
   },
+  watch: {
+    showModal (v) {
+      this.$emit('update:show', v)
+    },
+    show (v) {
+      this.showModal = v
+    }
+  },
   methods: {
     getErrorData (upload) {
-      let payload = upload.import_details || {}
-      let d = {
+      const payload = upload.import_details || {}
+      const d = {
         supportUrl: 'https://forum.funkwhale.audio/t/support',
         errorRows: []
       }
@@ -138,27 +197,19 @@ export default {
       if (d.errorCode === 'invalid_metadata') {
         d.label = this.$pgettext('Popup/Import/Error.Label', 'Invalid metadata')
         d.detail = this.$pgettext('Popup/Import/Error.Label', 'The metadata included in the file is invalid or some mandatory fields are missing.')
-        let detail = payload.detail || {}
+        const detail = payload.detail || {}
         d.errorRows = getErrors(detail)
       } else {
         d.label = this.$pgettext('*/*/Error', 'Unknown error')
         d.detail = this.$pgettext('Popup/Import/Error.Label', 'An unknown error occurred')
       }
-      let debugInfo = {
+      const debugInfo = {
         source: upload.source,
-        ...payload,
+        ...payload
       }
       d.debugInfo = JSON.stringify(debugInfo, null, 4)
       return d
     }
-  },
-  watch: {
-    showModal (v) {
-      this.$emit('update:show', v)
-    },
-    show (v) {
-      this.showModal = v
-    }
   }
 }
 </script>
diff --git a/front/src/components/library/Library.vue b/front/src/components/library/Library.vue
index 73d310715..784cc5aba 100644
--- a/front/src/components/library/Library.vue
+++ b/front/src/components/library/Library.vue
@@ -1,21 +1,21 @@
 <template>
   <div class="main pusher page-library">
-    <router-view :key="$router.currentRoute.fullPath"></router-view>
+    <router-view :key="$router.currentRoute.fullPath" />
   </div>
 </template>
 
 <script>
 export default {
   computed: {
-    showImports() {
+    showImports () {
       return (
-        this.$store.state.auth.availablePermissions["upload"] ||
-        this.$store.state.auth.availablePermissions["library"]
+        this.$store.state.auth.availablePermissions.upload ||
+        this.$store.state.auth.availablePermissions.library
       )
     },
-    labels() {
+    labels () {
       return {
-        secondaryMenu: this.$pgettext('Menu/*/Hidden text', "Secondary menu")
+        secondaryMenu: this.$pgettext('Menu/*/Hidden text', 'Secondary menu')
       }
     }
   }
diff --git a/front/src/components/library/Podcasts.vue b/front/src/components/library/Podcasts.vue
index 75607ab08..b003731be 100644
--- a/front/src/components/library/Podcasts.vue
+++ b/front/src/components/library/Podcasts.vue
@@ -2,60 +2,119 @@
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
       <h2 class="ui header">
-        <translate translate-context="Content/Podcasts/Title">Browsing podcasts</translate>
+        <translate translate-context="Content/Podcasts/Title">
+          Browsing podcasts
+        </translate>
       </h2>
-      <form :class="['ui', {'loading': isLoading}, 'form']" @submit.prevent="updatePage();updateQueryString();fetchData()">
+      <form
+        :class="['ui', {'loading': isLoading}, 'form']"
+        @submit.prevent="updatePage();updateQueryString();fetchData()"
+      >
         <div class="fields">
           <div class="field">
             <label for="artist-search">
               <translate translate-context="Content/Search/Input.Label/Noun">Podcast title</translate>
             </label>
             <div class="ui action input">
-              <input id="artist-search" type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
-              <button class="ui icon button" type="submit" :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')">
-                <i class="search icon"></i>
+              <input
+                id="artist-search"
+                v-model="query"
+                type="text"
+                name="search"
+                :placeholder="labels.searchPlaceholder"
+              >
+              <button
+                class="ui icon button"
+                type="submit"
+                :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')"
+              >
+                <i class="search icon" />
               </button>
             </div>
           </div>
           <div class="field">
             <label for="tags-search"><translate translate-context="*/*/*/Noun">Tags</translate></label>
-            <tags-selector v-model="tags"></tags-selector>
+            <tags-selector v-model="tags" />
           </div>
           <div class="field">
             <label for="artist-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-            <select id="artist-ordering" class="ui dropdown" v-model="ordering">
-              <option v-for="option in orderingOptions" :value="option[0]">
+            <select
+              id="artist-ordering"
+              v-model="ordering"
+              class="ui dropdown"
+            >
+              <option
+                v-for="(option, key) in orderingOptions"
+                :key="key"
+                :value="option[0]"
+              >
                 {{ sharedLabels.filters[option[1]] }}
               </option>
             </select>
           </div>
           <div class="field">
             <label for="artist-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
-            <select id="artist-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-              <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-              <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+            <select
+              id="artist-ordering-direction"
+              v-model="orderingDirection"
+              class="ui dropdown"
+            >
+              <option value="+">
+                <translate translate-context="Content/Search/Dropdown">
+                  Ascending
+                </translate>
+              </option>
+              <option value="-">
+                <translate translate-context="Content/Search/Dropdown">
+                  Descending
+                </translate>
+              </option>
             </select>
           </div>
           <div class="field">
             <label for="artist-results"><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label>
-            <select id="artist-results" class="ui dropdown" v-model="paginateBy">
-              <option :value="parseInt(12)">12</option>
-              <option :value="parseInt(30)">30</option>
-              <option :value="parseInt(50)">50</option>
+            <select
+              id="artist-results"
+              v-model="paginateBy"
+              class="ui dropdown"
+            >
+              <option :value="parseInt(12)">
+                12
+              </option>
+              <option :value="parseInt(30)">
+                30
+              </option>
+              <option :value="parseInt(50)">
+                50
+              </option>
             </select>
           </div>
         </div>
       </form>
-      <div class="ui hidden divider"></div>
-      <div v-if="result && result.results.length > 0" class="ui five app-cards cards">
-        <div v-if="isLoading" class="ui inverted active dimmer">
-          <div class="ui loader"></div>
+      <div class="ui hidden divider" />
+      <div
+        v-if="result && result.results.length > 0"
+        class="ui five app-cards cards"
+      >
+        <div
+          v-if="isLoading"
+          class="ui inverted active dimmer"
+        >
+          <div class="ui loader" />
         </div>
-        <artist-card :artist="artist" v-for="artist in result.results" :key="artist.id"></artist-card>
+        <artist-card
+          v-for="artist in result.results"
+          :key="artist.id"
+          :artist="artist"
+        />
       </div>
-      <div v-else-if="!isLoading" class="ui placeholder segment sixteen wide column" style="text-align: center; display: flex; align-items: center">
+      <div
+        v-else-if="!isLoading"
+        class="ui placeholder segment sixteen wide column"
+        style="text-align: center; display: flex; align-items: center"
+      >
         <div class="ui icon header">
-          <i class="podcast icon"></i>
+          <i class="podcast icon" />
           <translate translate-context="Content/Artists/Placeholder">
             No results matching your query
           </translate>
@@ -63,184 +122,204 @@
         <router-link
           v-if="$store.state.auth.authenticated"
           :to="{name: 'content.index'}"
-          class="ui success button labeled icon">
-          <i class="upload icon"></i>
+          class="ui success button labeled icon"
+        >
+          <i class="upload icon" />
           <translate translate-context="Content/*/Verb">
             Create a Channel
           </translate>
         </router-link>
-        <h1 v-if ="$store.state.auth.authenticated" class="ui with-actions header">
-        <div class="actions">
-          <a @click.stop.prevent="showSubscribeModal = true">
-            <i class="plus icon"></i>
-            <translate translate-context="Content/Profile/Button">Subscribe to feed</translate>
-          </a>
-        </div>
-      </h1>
+        <h1
+          v-if="$store.state.auth.authenticated"
+          class="ui with-actions header"
+        >
+          <div class="actions">
+            <a @click.stop.prevent="showSubscribeModal = true">
+              <i class="plus icon" />
+              <translate translate-context="Content/Profile/Button">Subscribe to feed</translate>
+            </a>
+          </div>
+        </h1>
       </div>
       <div class="ui center aligned basic segment">
         <pagination
           v-if="result && result.count > paginateBy"
-          @page-changed="selectPage"
           :current="page"
           :paginate-by="paginateBy"
           :total="result.count"
-          ></pagination>
+          @page-changed="selectPage"
+        />
       </div>
     </section>
-    <modal class="tiny" :show.sync="showSubscribeModal" :fullscreen="false">
-        <h2 class="header">
-          <translate translate-context="*/*/*/Noun">Subscription</translate>
-        </h2>
-        <div class="scrolling content" ref="modalContent">
-          <remote-search-form
-            type="both"
-            :show-submit="false"
-            :standalone="false"
-            @subscribed="showSubscribeModal = false; fetchData()"
-            :redirect="true"></remote-search-form>
-        </div>
-        <div class="actions">
-          <button class="ui basic deny button">
-            <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
-          </button>
-          <button form="remote-search" type="submit" class="ui primary button">
-            <i class="bookmark icon"></i>
-            <translate translate-context="*/*/*/Verb">Subscribe</translate>
-          </button>
-        </div>
-      </modal>
-
+    <modal
+      class="tiny"
+      :show.sync="showSubscribeModal"
+      :fullscreen="false"
+    >
+      <h2 class="header">
+        <translate translate-context="*/*/*/Noun">
+          Subscription
+        </translate>
+      </h2>
+      <div
+        ref="modalContent"
+        class="scrolling content"
+      >
+        <remote-search-form
+          type="both"
+          :show-submit="false"
+          :standalone="false"
+          :redirect="true"
+          @subscribed="showSubscribeModal = false; fetchData()"
+        />
+      </div>
+      <div class="actions">
+        <button class="ui basic deny button">
+          <translate translate-context="*/*/Button.Label/Verb">
+            Cancel
+          </translate>
+        </button>
+        <button
+          form="remote-search"
+          type="submit"
+          class="ui primary button"
+        >
+          <i class="bookmark icon" />
+          <translate translate-context="*/*/*/Verb">
+            Subscribe
+          </translate>
+        </button>
+      </div>
+    </modal>
   </main>
 </template>
 
 <script>
 import qs from 'qs'
-import axios from "axios"
-import _ from "@/lodash"
-import $ from "jquery"
+import axios from 'axios'
+import $ from 'jquery'
 
-import logger from "@/logging"
+import logger from '@/logging'
 
-import OrderingMixin from "@/components/mixins/Ordering"
-import PaginationMixin from "@/components/mixins/Pagination"
-import TranslationsMixin from "@/components/mixins/Translations"
-import ArtistCard from "@/components/audio/artist/Card"
-import Pagination from "@/components/Pagination"
+import OrderingMixin from '@/components/mixins/Ordering'
+import PaginationMixin from '@/components/mixins/Pagination'
+import TranslationsMixin from '@/components/mixins/Translations'
+import ArtistCard from '@/components/audio/artist/Card'
+import Pagination from '@/components/Pagination'
 import TagsSelector from '@/components/library/TagsSelector'
 import Modal from '@/components/semantic/Modal'
-import RemoteSearchForm from "@/components/RemoteSearchForm"
+import RemoteSearchForm from '@/components/RemoteSearchForm'
 
-const FETCH_URL = "artists/"
+const FETCH_URL = 'artists/'
 
 export default {
-  mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
-  props: {
-    defaultQuery: { type: String, required: false, default: "" },
-    defaultTags: { type: Array, required: false, default: () => { return [] } },
-    scope: { type: String, required: false, default: "all" },
-  },
   components: {
     ArtistCard,
     Pagination,
     TagsSelector,
     RemoteSearchForm,
-    Modal,
+    Modal
+  },
+  mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
+  props: {
+    defaultQuery: { type: String, required: false, default: '' },
+    defaultTags: { type: Array, required: false, default: () => { return [] } },
+    scope: { type: String, required: false, default: 'all' }
   },
-  data() {
+  data () {
     return {
       isLoading: true,
       result: null,
       page: parseInt(this.defaultPage),
       query: this.defaultQuery,
       tags: (this.defaultTags || []).filter((t) => { return t.length > 0 }),
-      orderingOptions: [["creation_date", "creation_date"], ["name", "name"]],
-      showSubscribeModal: false,
+      orderingOptions: [['creation_date', 'creation_date'], ['name', 'name']],
+      showSubscribeModal: false
     }
   },
-  created() {
-    this.fetchData()
-  },
-  mounted() {
-    $(".ui.dropdown").dropdown()
-  },
   computed: {
-    labels() {
-      let searchPlaceholder = this.$pgettext('Content/Search/Input.Placeholder', "Search…")
-      let title = this.$pgettext('*/*/*/Noun', "Podcasts")
+    labels () {
+      const searchPlaceholder = this.$pgettext('Content/Search/Input.Placeholder', 'Search…')
+      const title = this.$pgettext('*/*/*/Noun', 'Podcasts')
       return {
         searchPlaceholder,
         title
       }
     }
   },
+  watch: {
+    page () {
+      this.updateQueryString()
+      this.fetchData()
+    },
+    '$store.state.moderation.lastUpdate': function () {
+      this.fetchData()
+    },
+    excludeCompilation () {
+      this.fetchData()
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  mounted () {
+    $('.ui.dropdown').dropdown()
+  },
   methods: {
-    updateQueryString: function() {
+    updateQueryString: function () {
       history.pushState(
         {},
         null,
         this.$route.path + '?' + new URLSearchParams(
           {
-          query: this.query,
-          page: this.page,
-          tag: this.tags,
-          paginateBy: this.paginateBy,
-          ordering: this.getOrderingAsString(),
-          include_channels: true,
-          content_category: 'podcast',
-        }).toString()
+            query: this.query,
+            page: this.page,
+            tag: this.tags,
+            paginateBy: this.paginateBy,
+            ordering: this.getOrderingAsString(),
+            include_channels: true,
+            content_category: 'podcast'
+          }).toString()
       )
     },
-    fetchData: function() {
-      var self = this
+    fetchData: function () {
+      const self = this
       this.isLoading = true
-      let url = FETCH_URL
-      let params = {
+      const url = FETCH_URL
+      const params = {
         scope: this.scope,
         page: this.page,
         page_size: this.paginateBy,
         has_albums: this.excludeCompilation,
         q: this.query,
         ordering: this.getOrderingAsString(),
-        playable: "true",
+        playable: 'true',
         tag: this.tags,
-        include_channels: "true",
-        content_category: 'podcast',
+        include_channels: 'true',
+        content_category: 'podcast'
       }
-      logger.default.debug("Fetching artists")
+      logger.default.debug('Fetching artists')
       axios.get(
         url,
         {
           params: params,
-          paramsSerializer: function(params) {
+          paramsSerializer: function (params) {
             return qs.stringify(params, { indices: false })
           }
         }
       ).then(response => {
         self.result = response.data
         self.isLoading = false
-      }, error => {
+      }, () => {
         self.result = null
         self.isLoading = false
       })
     },
-    selectPage: function(page) {
+    selectPage: function (page) {
       this.page = page
     },
-    updatePage() {
+    updatePage () {
       this.page = this.defaultPage
-    },
-  },
-  watch: {
-    page() {
-      this.updateQueryString()
-      this.fetchData()
-    },
-    "$store.state.moderation.lastUpdate": function () {
-      this.fetchData()
-    },
-    excludeCompilation() {
-      this.fetchData()
     }
   }
 }
diff --git a/front/src/components/library/Radios.vue b/front/src/components/library/Radios.vue
index 8f2b551ae..feb946b99 100644
--- a/front/src/components/library/Radios.vue
+++ b/front/src/components/library/Radios.vue
@@ -2,83 +2,148 @@
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
       <h2 class="ui header">
-        <translate translate-context="Content/Radio/Title">Browsing radios</translate>
+        <translate translate-context="Content/Radio/Title">
+          Browsing radios
+        </translate>
       </h2>
-      <div class="ui hidden divider"></div>
+      <div class="ui hidden divider" />
       <div class="ui row">
         <h3 class="ui header">
-          <translate translate-context="Content/Radio/Title">Instance radios</translate>
+          <translate translate-context="Content/Radio/Title">
+            Instance radios
+          </translate>
         </h3>
         <div class="ui cards">
-          <radio-card v-if="isAuthenticated" :type="'actor-content'" :object-id="$store.state.auth.fullUsername"></radio-card>
-          <radio-card v-if="isAuthenticated && hasFavorites" :type="'favorites'"></radio-card>
-          <radio-card :type="'random'"></radio-card>
-          <radio-card :type="'recently-added'"></radio-card>
-          <radio-card v-if="$store.state.auth.authenticated" :type="'less-listened'"></radio-card>
+          <radio-card
+            v-if="isAuthenticated"
+            :type="'actor-content'"
+            :object-id="$store.state.auth.fullUsername"
+          />
+          <radio-card
+            v-if="isAuthenticated && hasFavorites"
+            :type="'favorites'"
+          />
+          <radio-card :type="'random'" />
+          <radio-card :type="'recently-added'" />
+          <radio-card
+            v-if="$store.state.auth.authenticated"
+            :type="'less-listened'"
+          />
         </div>
       </div>
 
-      <div class="ui hidden divider"></div>
+      <div class="ui hidden divider" />
       <h3 class="ui header">
-        <translate translate-context="Content/Radio/Title">User radios</translate>
+        <translate translate-context="Content/Radio/Title">
+          User radios
+        </translate>
       </h3>
-      <router-link class="ui success button" to="/library/radios/build" exact>
-        <translate translate-context="Content/Radio/Button.Label/Verb">Create your own radio</translate>
+      <router-link
+        class="ui success button"
+        to="/library/radios/build"
+        exact
+      >
+        <translate translate-context="Content/Radio/Button.Label/Verb">
+          Create your own radio
+        </translate>
       </router-link>
-      <div class="ui hidden divider"></div>
-      <form :class="['ui', {'loading': isLoading}, 'form']" @submit.prevent="updateQueryString();fetchData()">
+      <div class="ui hidden divider" />
+      <form
+        :class="['ui', {'loading': isLoading}, 'form']"
+        @submit.prevent="updateQueryString();fetchData()"
+      >
         <div class="fields">
           <div class="field">
             <label for="radios-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
             <div class="ui action input">
-              <input id ="radios-search" type="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
-              <button class="ui icon button" type="submit" :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')">
-                <i class="search icon"></i>
+              <input
+                id="radios-search"
+                v-model="query"
+                type="text"
+                name="search"
+                :placeholder="labels.searchPlaceholder"
+              >
+              <button
+                class="ui icon button"
+                type="submit"
+                :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')"
+              >
+                <i class="search icon" />
               </button>
             </div>
           </div>
           <div class="field">
             <label for="radios-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-            <select id="radios-ordering" class="ui dropdown" v-model="ordering">
-              <option v-for="option in orderingOptions" :value="option[0]">
+            <select
+              id="radios-ordering"
+              v-model="ordering"
+              class="ui dropdown"
+            >
+              <option
+                v-for="(option, key) in orderingOptions"
+                :key="key"
+                :value="option[0]"
+              >
                 {{ sharedLabels.filters[option[1]] }}
               </option>
             </select>
           </div>
           <div class="field">
             <label for="radios-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label>
-            <select id="radios-ordering-direction" class="ui dropdown" v-model="orderingDirection">
+            <select
+              id="radios-ordering-direction"
+              v-model="orderingDirection"
+              class="ui dropdown"
+            >
               <option value="+">
-                <translate translate-context="Content/Search/Dropdown">Ascending</translate>
+                <translate translate-context="Content/Search/Dropdown">
+                  Ascending
+                </translate>
               </option>
               <option value="-">
-                <translate translate-context="Content/Search/Dropdown">Descending</translate>
+                <translate translate-context="Content/Search/Dropdown">
+                  Descending
+                </translate>
               </option>
             </select>
           </div>
           <div class="field">
             <label for="radios-results"><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label>
-            <select id="radios-results" class="ui dropdown" v-model="paginateBy">
-              <option :value="parseInt(12)">12</option>
-              <option :value="parseInt(25)">25</option>
-              <option :value="parseInt(50)">50</option>
+            <select
+              id="radios-results"
+              v-model="paginateBy"
+              class="ui dropdown"
+            >
+              <option :value="parseInt(12)">
+                12
+              </option>
+              <option :value="parseInt(25)">
+                25
+              </option>
+              <option :value="parseInt(50)">
+                50
+              </option>
             </select>
           </div>
         </div>
       </form>
-      <div class="ui hidden divider"></div>
-      <div v-if="result && !result.results.length > 0" class="ui placeholder segment">
+      <div class="ui hidden divider" />
+      <div
+        v-if="result && !result.results.length > 0"
+        class="ui placeholder segment"
+      >
         <div class="ui icon header">
-          <i class="feed icon"></i>
+          <i class="feed icon" />
           <translate translate-context="Content/Radios/Placeholder">
             No results matching your query
           </translate>
         </div>
         <router-link
-        v-if="$store.state.auth.authenticated"
-        :to="{name: 'library.radios.build'}"
-        class="ui success button labeled icon">
-          <i class="rss icon"></i>
+          v-if="$store.state.auth.authenticated"
+          :to="{name: 'library.radios.build'}"
+          class="ui success button labeled icon"
+        >
+          <i class="rss icon" />
           <translate translate-context="Content/*/Verb">
             Create a radio
           </translate>
@@ -86,70 +151,65 @@
       </div>
       <div
         v-if="result && result.results.length > 0"
-        class="ui cards">
+        class="ui cards"
+      >
         <radio-card
-          type="custom"
           v-for="radio in result.results"
           :key="radio.id"
-          :custom-radio="radio"></radio-card>
+          type="custom"
+          :custom-radio="radio"
+        />
       </div>
       <div class="ui center aligned basic segment">
         <pagination
           v-if="result && result.count > paginateBy"
-          @page-changed="selectPage"
           :current="page"
           :paginate-by="paginateBy"
           :total="result.count"
-          ></pagination>
+          @page-changed="selectPage"
+        />
       </div>
     </section>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import _ from "@/lodash"
-import $ from "jquery"
+import axios from 'axios'
+import $ from 'jquery'
 
-import logger from "@/logging"
+import logger from '@/logging'
 
-import OrderingMixin from "@/components/mixins/Ordering"
-import PaginationMixin from "@/components/mixins/Pagination"
-import TranslationsMixin from "@/components/mixins/Translations"
-import RadioCard from "@/components/radios/Card"
-import Pagination from "@/components/Pagination"
+import OrderingMixin from '@/components/mixins/Ordering'
+import PaginationMixin from '@/components/mixins/Pagination'
+import TranslationsMixin from '@/components/mixins/Translations'
+import RadioCard from '@/components/radios/Card'
+import Pagination from '@/components/Pagination'
 
-const FETCH_URL = "radios/radios/"
+const FETCH_URL = 'radios/radios/'
 
 export default {
-  mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
-  props: {
-    defaultQuery: { type: String, required: false, default: "" },
-    scope: { type: String, required: false, default: "all" },
-  },
   components: {
     RadioCard,
     Pagination
   },
-  data() {
+  mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
+  props: {
+    defaultQuery: { type: String, required: false, default: '' },
+    scope: { type: String, required: false, default: 'all' }
+  },
+  data () {
     return {
       isLoading: true,
       result: null,
       page: parseInt(this.defaultPage),
       query: this.defaultQuery,
-      orderingOptions: [["creation_date", "creation_date"], ["name", "name"]]
+      orderingOptions: [['creation_date', 'creation_date'], ['name', 'name']]
     }
   },
-  created() {
-    this.fetchData()
-  },
-  mounted() {
-    $(".ui.dropdown").dropdown()
-  },
   computed: {
-    labels() {
-      let searchPlaceholder = this.$pgettext('Content/Search/Input.Placeholder', "Enter a radio name…")
-      let title = this.$pgettext('*/*/*', "Radios")
+    labels () {
+      const searchPlaceholder = this.$pgettext('Content/Search/Input.Placeholder', 'Enter a radio name…')
+      const title = this.$pgettext('*/*/*', 'Radios')
       return {
         searchPlaceholder,
         title
@@ -160,48 +220,54 @@ export default {
     },
     hasFavorites () {
       return this.$store.state.favorites.count > 0
-    },
+    }
+  },
+  watch: {
+    page () {
+      this.updateQueryString()
+      this.fetchData()
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  mounted () {
+    $('.ui.dropdown').dropdown()
   },
   methods: {
-    updateQueryString: function() {
+    updateQueryString: function () {
       history.pushState(
         {},
         null,
         this.$route.path + '?' + new URLSearchParams(
           {
-          query: this.query,
-          page: this.page,
-          paginateBy: this.paginateBy,
-          ordering: this.getOrderingAsString()
-        }).toString()
+            query: this.query,
+            page: this.page,
+            paginateBy: this.paginateBy,
+            ordering: this.getOrderingAsString()
+          }).toString()
       )
     },
-    fetchData: function() {
-      var self = this
+    fetchData: function () {
+      const self = this
       this.isLoading = true
-      let url = FETCH_URL
-      let params = {
+      const url = FETCH_URL
+      const params = {
         scope: this.scope,
         page: this.page,
         page_size: this.paginateBy,
         name__icontains: this.query,
-        ordering: this.getOrderingAsString(),
+        ordering: this.getOrderingAsString()
       }
-      logger.default.debug("Fetching radios")
+      logger.default.debug('Fetching radios')
       axios.get(url, { params: params }).then(response => {
         self.result = response.data
         self.isLoading = false
       })
     },
-    selectPage: function(page) {
+    selectPage: function (page) {
       this.page = page
     }
-  },
-  watch: {
-    page() {
-      this.updateQueryString()
-      this.fetchData()
-    },
   }
 }
 </script>
diff --git a/front/src/components/library/TagDetail.vue b/front/src/components/library/TagDetail.vue
index 9706153a4..2ede588e3 100644
--- a/front/src/components/library/TagDetail.vue
+++ b/front/src/components/library/TagDetail.vue
@@ -6,70 +6,109 @@
           {{ labels.title }}
         </span>
       </h2>
-      <radio-button type="tag" :object-id="id"></radio-button>
-      <router-link class="ui right floated button" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.tags.detail', params: {id: id}}">
-        <i class="wrench icon"></i>
-        <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
+      <radio-button
+        type="tag"
+        :object-id="id"
+      />
+      <router-link
+        v-if="$store.state.auth.availablePermissions['library']"
+        class="ui right floated button"
+        :to="{name: 'manage.library.tags.detail', params: {id: id}}"
+      >
+        <i class="wrench icon" />
+        <translate translate-context="Content/Moderation/Link">
+          Open in moderation interface
+        </translate>
       </router-link>
 
-      <div class="ui hidden divider"></div>
+      <div class="ui hidden divider" />
       <div class="ui row">
-        <artist-widget :key="id" :controls="false" :filters="{playable: true, ordering: '-creation_date', tag: id, include_channels: 'false'}">
+        <artist-widget
+          :key="id"
+          :controls="false"
+          :filters="{playable: true, ordering: '-creation_date', tag: id, include_channels: 'false'}"
+        >
           <template slot="title">
             <router-link :to="{name: 'library.artists.browse', query: {tag: id}}">
-              <translate translate-context="*/*/*/Noun">Artists</translate>
+              <translate translate-context="*/*/*/Noun">
+                Artists
+              </translate>
             </router-link>
           </template>
         </artist-widget>
-        <div class="ui hidden divider"></div>
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
+        <div class="ui hidden divider" />
         <h3 class="ui header">
-          <translate translate-context="*/*/*">Channels</translate>
+          <translate translate-context="*/*/*">
+            Channels
+          </translate>
         </h3>
-        <channels-widget :key="id" :show-modification-date="true" :limit="12" :filters="{tag: id, ordering: '-creation_date'}"></channels-widget>
-        <div class="ui hidden divider"></div>
-        <div class="ui hidden divider"></div>
-        <album-widget :key="id" :show-count="true" :controls="false" :filters="{playable: true, ordering: '-creation_date', tag: id}">
+        <channels-widget
+          :key="id"
+          :show-modification-date="true"
+          :limit="12"
+          :filters="{tag: id, ordering: '-creation_date'}"
+        />
+        <div class="ui hidden divider" />
+        <div class="ui hidden divider" />
+        <album-widget
+          :key="id"
+          :show-count="true"
+          :controls="false"
+          :filters="{playable: true, ordering: '-creation_date', tag: id}"
+        >
           <template slot="title">
             <router-link :to="{name: 'library.albums.browse', query: {tag: id}}">
-              <translate translate-context="*/*/*">Albums</translate>
+              <translate translate-context="*/*/*">
+                Albums
+              </translate>
             </router-link>
           </template>
         </album-widget>
-        <div class="ui hidden divider"></div>
-        <div class="ui hidden divider"></div>
-        <track-widget :key="id" :show-count="true" :limit="12" item-classes="track-item inline" :url="'/tracks/'" :is-activity="false" :filters="{playable: true, ordering: '-creation_date', tag: id}">
+        <div class="ui hidden divider" />
+        <div class="ui hidden divider" />
+        <track-widget
+          :key="id"
+          :show-count="true"
+          :limit="12"
+          item-classes="track-item inline"
+          :url="'/tracks/'"
+          :is-activity="false"
+          :filters="{playable: true, ordering: '-creation_date', tag: id}"
+        >
           <template slot="title">
-            <translate translate-context="*/*/*">Tracks</translate>
+            <translate translate-context="*/*/*">
+              Tracks
+            </translate>
           </template>
         </track-widget>
-        <div class="ui clearing hidden divider"></div>
+        <div class="ui clearing hidden divider" />
       </div>
     </section>
   </main>
 </template>
 
 <script>
-import ChannelsWidget from "@/components/audio/ChannelsWidget"
-import TrackWidget from "@/components/audio/track/Widget"
-import AlbumWidget from "@/components/audio/album/Widget"
-import ArtistWidget from "@/components/audio/artist/Widget"
-import RadioButton from "@/components/radios/Button"
+import ChannelsWidget from '@/components/audio/ChannelsWidget'
+import TrackWidget from '@/components/audio/track/Widget'
+import AlbumWidget from '@/components/audio/album/Widget'
+import ArtistWidget from '@/components/audio/artist/Widget'
+import RadioButton from '@/components/radios/Button'
 
 export default {
-  props: {
-    id: { type: String, required: true }
-  },
   components: {
     ArtistWidget,
     AlbumWidget,
     TrackWidget,
     RadioButton,
-    ChannelsWidget,
+    ChannelsWidget
+  },
+  props: {
+    id: { type: String, required: true }
   },
   computed: {
-    labels() {
-      let title = `#${this.id}`
+    labels () {
+      const title = `#${this.id}`
       return {
         title
       }
@@ -79,7 +118,7 @@ export default {
     },
     hasFavorites () {
       return this.$store.state.favorites.count > 0
-    },
-  },
+    }
+  }
 }
 </script>
diff --git a/front/src/components/library/TagsSelector.vue b/front/src/components/library/TagsSelector.vue
index c19a5ece4..c968bb72c 100644
--- a/front/src/components/library/TagsSelector.vue
+++ b/front/src/components/library/TagsSelector.vue
@@ -1,10 +1,19 @@
 <template>
-  <div ref="dropdown" class="ui multiple search selection dropdown">
+  <div
+    ref="dropdown"
+    class="ui multiple search selection dropdown"
+  >
     <input type="hidden">
-    <i class="dropdown icon"></i>
-    <input id="tags-search" type="text" class="search">
+    <i class="dropdown icon" />
+    <input
+      id="tags-search"
+      type="text"
+      class="search"
+    >
     <div class="default text">
-      <translate translate-context="*/Dropdown/Placeholder/Verb">Search…</translate>
+      <translate translate-context="*/Dropdown/Placeholder/Verb">
+        Search…
+      </translate>
     </div>
   </div>
 </template>
@@ -13,54 +22,62 @@ import $ from 'jquery'
 
 import lodash from '@/lodash'
 export default {
-  props: ['value'],
+  props: { value: { type: String, required: true } },
+  watch: {
+    value: {
+      handler (v) {
+        const current = $(this.$refs.dropdown).dropdown('get value').split(',').sort()
+        if (!lodash.isEqual([...v].sort(), current)) {
+          $(this.$refs.dropdown).dropdown('set exactly', v)
+        }
+      },
+      deep: true
+    }
+  },
   mounted () {
     this.$nextTick(() => {
       this.initDropdown()
-
     })
   },
   methods: {
     initDropdown () {
-      let self = this
-      let handleUpdate = () => {
-        let value = $(self.$refs.dropdown).dropdown('get value').split(',')
+      const self = this
+      const handleUpdate = () => {
+        const value = $(self.$refs.dropdown).dropdown('get value').split(',')
         self.$emit('input', value)
         return value
       }
-      let settings = {
-        keys : {
-          delimiter  : 32,
+      const settings = {
+        keys: {
+          delimiter: 32
         },
         forceSelection: false,
         saveRemoteData: false,
         filterRemoteData: true,
-        preserveHTML : false,
+        preserveHTML: false,
         apiSettings: {
           url: this.$store.getters['instance/absoluteUrl']('/api/v1/tags/?name__startswith={query}&ordering=length&page_size=5'),
           beforeXHR: function (xhrObject) {
-
             if (self.$store.state.auth.oauth.accessToken) {
               xhrObject.setRequestHeader('Authorization', self.$store.getters['auth/header'])
             }
             return xhrObject
           },
-          onResponse(response) {
-            let currentSearch = $(self.$refs.dropdown).dropdown('get query')
+          onResponse (response) {
+            const currentSearch = $(self.$refs.dropdown).dropdown('get query')
             response = {
               results: [],
-              ...response,
+              ...response
             }
             if (currentSearch) {
-              let existingTag = response.results.find((result) => result.name === currentSearch)
+              const existingTag = response.results.find((result) => result.name === currentSearch)
               if (existingTag) {
                 if (response.results.indexOf(existingTag) !== 0) {
                   response.results = [existingTag, ...response.results]
                   response.results.splice(response.results.indexOf(existingTag) + 1, 1)
                 }
-              }
-              else {
-                response.results = [{name: currentSearch}, ...response.results]
+              } else {
+                response.results = [{ name: currentSearch }, ...response.results]
               }
             }
             return response
@@ -75,22 +92,11 @@ export default {
         onAdd: handleUpdate,
         onRemove: handleUpdate,
         onLabelRemove: handleUpdate,
-        onChange: handleUpdate,
+        onChange: handleUpdate
       }
       $(this.$refs.dropdown).dropdown(settings)
       $(this.$refs.dropdown).dropdown('set exactly', this.value)
     }
-  },
-  watch: {
-    value: {
-      handler (v) {
-        let current = $(this.$refs.dropdown).dropdown('get value').split(',').sort()
-        if (!lodash.isEqual([...v].sort(), current)) {
-          $(this.$refs.dropdown).dropdown('set exactly', v)
-        }
-      },
-      deep: true
-    }
   }
 }
 </script>
diff --git a/front/src/components/library/TrackBase.vue b/front/src/components/library/TrackBase.vue
index cd8447c08..5107cc58a 100644
--- a/front/src/components/library/TrackBase.vue
+++ b/front/src/components/library/TrackBase.vue
@@ -1,112 +1,203 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment" v-title="labels.title">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      v-title="labels.title"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="track">
       <section
-        :class="['ui', 'head', 'vertical', 'center', 'aligned', 'stripe', 'segment']"
         v-title="track.title"
+        :class="['ui', 'head', 'vertical', 'center', 'aligned', 'stripe', 'segment']"
       >
         <div class="ui basic padded segment">
           <div class="ui stackable grid row container">
             <div class="eight wide left aligned column">
               <h1 class="ui header">
                 {{ track.title }}
-                <div class="sub header" v-html="subtitle"></div>
+                <div
+                  class="sub header"
+                  v-html="subtitle"
+                />
               </h1>
             </div>
             <div class="eight wide right aligned column button-group">
-              <play-button class="vibrant" :track="track">
-                <translate translate-context="*/Queue/Button.Label/Short, Verb">Play</translate>
+              <play-button
+                class="vibrant"
+                :track="track"
+              >
+                <translate translate-context="*/Queue/Button.Label/Short, Verb">
+                  Play
+                </translate>
               </play-button>
               &nbsp;
-              <track-favorite-icon v-if="$store.state.auth.authenticated" :border="true" :track="track"></track-favorite-icon>
-              <track-playlist-icon class="circular" v-if="$store.state.auth.authenticated" :border="true" :track="track"></track-playlist-icon>
-              <a role="button" :aria-label="labels.download" v-if="upload" :href="downloadUrl" target="_blank" class="ui basic circular icon button" :title="labels.download">
-                <i class="download icon"></i>
+              <track-favorite-icon
+                v-if="$store.state.auth.authenticated"
+                :border="true"
+                :track="track"
+              />
+              <track-playlist-icon
+                v-if="$store.state.auth.authenticated"
+                class="circular"
+                :border="true"
+                :track="track"
+              />
+              <a
+                v-if="upload"
+                role="button"
+                :aria-label="labels.download"
+                :href="downloadUrl"
+                target="_blank"
+                class="ui basic circular icon button"
+                :title="labels.download"
+              >
+                <i class="download icon" />
               </a>
-              <modal v-if="isEmbedable" :show.sync="showEmbedModal">
+              <modal
+                v-if="isEmbedable"
+                :show.sync="showEmbedModal"
+              >
                 <h4 class="header">
-                  <translate translate-context="Popup/Track/Title">Embed this track on your website</translate>
+                  <translate translate-context="Popup/Track/Title">
+                    Embed this track on your website
+                  </translate>
                 </h4>
                 <div class="scrolling content">
                   <div class="description">
-                    <embed-wizard type="track" :id="track.id" />
+                    <embed-wizard
+                      :id="track.id"
+                      type="track"
+                    />
                   </div>
                 </div>
                 <div class="actions">
                   <button class="ui basic deny button">
-                    <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+                    <translate translate-context="*/*/Button.Label/Verb">
+                      Cancel
+                    </translate>
                   </button>
                 </div>
               </modal>
-              <button class="ui floating dropdown circular icon basic button" :title="labels.more" v-dropdown="{direction: 'downward'}">
-                <i class="ellipsis vertical icon"></i>
-                <div class="menu" style="right: 0; left: auto">
+              <button
+                v-dropdown="{direction: 'downward'}"
+                class="ui floating dropdown circular icon basic button"
+                :title="labels.more"
+              >
+                <i class="ellipsis vertical icon" />
+                <div
+                  class="menu"
+                  style="right: 0; left: auto"
+                >
                   <a
-                    :href="track.fid"
                     v-if="domain != $store.getters['instance/domain']"
+                    :href="track.fid"
                     target="_blank"
-                    class="basic item">
-                    <i class="external icon"></i>
-                    <translate :translate-params="{domain: domain}" translate-context="Content/*/Button.Label/Verb">View on %{ domain }</translate>
+                    class="basic item"
+                  >
+                    <i class="external icon" />
+                    <translate
+                      :translate-params="{domain: domain}"
+                      translate-context="Content/*/Button.Label/Verb"
+                    >View on %{ domain }</translate>
                   </a>
                   <div
-                    role="button"
                     v-if="isEmbedable"
+                    role="button"
+                    class="basic item"
                     @click="showEmbedModal = !showEmbedModal"
-                    class="basic item">
-                    <i class="code icon"></i>
-                    <translate translate-context="Content/*/Button.Label/Verb">Embed</translate>
+                  >
+                    <i class="code icon" />
+                    <translate translate-context="Content/*/Button.Label/Verb">
+                      Embed
+                    </translate>
                   </div>
-                  <a :href="wikipediaUrl" target="_blank" rel="noreferrer noopener" class="basic item">
-                    <i class="wikipedia w icon"></i>
+                  <a
+                    :href="wikipediaUrl"
+                    target="_blank"
+                    rel="noreferrer noopener"
+                    class="basic item"
+                  >
+                    <i class="wikipedia w icon" />
                     <translate translate-context="Content/*/Button.Label/Verb">Search on Wikipedia</translate>
                   </a>
-                  <a v-if="discogsUrl" :href="discogsUrl" target="_blank" rel="noreferrer noopener" class="basic item">
-                    <i class="external icon"></i>
+                  <a
+                    v-if="discogsUrl"
+                    :href="discogsUrl"
+                    target="_blank"
+                    rel="noreferrer noopener"
+                    class="basic item"
+                  >
+                    <i class="external icon" />
                     <translate translate-context="Content/*/Button.Label/Verb">Search on Discogs</translate>
                   </a>
                   <router-link
                     v-if="track.is_local"
                     :to="{name: 'library.tracks.edit', params: {id: track.id }}"
-                    class="basic item">
-                    <i class="edit icon"></i>
-                    <translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
+                    class="basic item"
+                  >
+                    <i class="edit icon" />
+                    <translate translate-context="Content/*/Button.Label/Verb">
+                      Edit
+                    </translate>
                   </router-link>
                   <dangerous-button
-                    :class="['ui', {loading: isLoading}, 'item']"
                     v-if="artist && $store.state.auth.authenticated && artist.channel && artist.attributed_to.full_username === $store.state.auth.fullUsername"
-                    @confirm="remove()">
-                    <i class="ui trash icon"></i>
-                    <translate translate-context="*/*/*/Verb">Delete…</translate>
-                    <p slot="modal-header"><translate translate-context="Popup/Channel/Title">Delete this track?</translate></p>
+                    :class="['ui', {loading: isLoading}, 'item']"
+                    @confirm="remove()"
+                  >
+                    <i class="ui trash icon" />
+                    <translate translate-context="*/*/*/Verb">
+                      Delete…
+                    </translate>
+                    <p slot="modal-header">
+                      <translate translate-context="Popup/Channel/Title">
+                        Delete this track?
+                      </translate>
+                    </p>
                     <div slot="modal-content">
-                      <p><translate translate-context="Content/Moderation/Paragraph">The track will be deleted, as well as any related files and data. This action is irreversible.</translate></p>
+                      <p>
+                        <translate translate-context="Content/Moderation/Paragraph">
+                          The track will be deleted, as well as any related files and data. This action is irreversible.
+                        </translate>
+                      </p>
                     </div>
-                    <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p>
+                    <p slot="modal-confirm">
+                      <translate translate-context="*/*/*/Verb">
+                        Delete
+                      </translate>
+                    </p>
                   </dangerous-button>
-                  <div class="divider"></div>
+                  <div class="divider" />
                   <div
-                    role="button"
-                    class="basic item"
                     v-for="obj in getReportableObjs({track})"
                     :key="obj.target.type + obj.target.id"
-                    @click.stop.prevent="$store.dispatch('moderation/report', obj.target)">
+                    role="button"
+                    class="basic item"
+                    @click.stop.prevent="$store.dispatch('moderation/report', obj.target)"
+                  >
                     <i class="share icon" /> {{ obj.label }}
                   </div>
-                  <div class="divider"></div>
-                  <router-link class="basic item" v-if="$store.state.auth.availablePermissions['library']" :to="{name: 'manage.library.tracks.detail', params: {id: track.id}}">
-                    <i class="wrench icon"></i>
-                    <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
+                  <div class="divider" />
+                  <router-link
+                    v-if="$store.state.auth.availablePermissions['library']"
+                    class="basic item"
+                    :to="{name: 'manage.library.tracks.detail', params: {id: track.id}}"
+                  >
+                    <i class="wrench icon" />
+                    <translate translate-context="Content/Moderation/Link">
+                      Open in moderation interface
+                    </translate>
                   </router-link>
                   <a
                     v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                     class="basic item"
                     :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/track/${track.id}`)"
-                    target="_blank" rel="noopener noreferrer">
-                    <i class="wrench icon"></i>
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >
+                    <i class="wrench icon" />
                     <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                   </a>
                 </div>
@@ -115,50 +206,54 @@
           </div>
         </div>
       </section>
-      <router-view v-if="track" @libraries-loaded="libraries = $event" :track="track" :object="track" object-type="track" :key="$route.fullPath"></router-view>
+      <router-view
+        v-if="track"
+        :key="$route.fullPath"
+        :track="track"
+        :object="track"
+        object-type="track"
+        @libraries-loaded="libraries = $event"
+      />
     </template>
   </main>
 </template>
 
 <script>
-import time from "@/utils/time"
-import axios from "axios"
-import url from "@/utils/url"
-import {getDomain} from '@/utils'
-import logger from "@/logging"
-import PlayButton from "@/components/audio/PlayButton"
-import TrackFavoriteIcon from "@/components/favorites/TrackFavoriteIcon"
-import TrackPlaylistIcon from "@/components/playlists/TrackPlaylistIcon"
+import time from '@/utils/time'
+import axios from 'axios'
+import url from '@/utils/url'
+import { getDomain } from '@/utils'
+import logger from '@/logging'
+import PlayButton from '@/components/audio/PlayButton'
+import TrackFavoriteIcon from '@/components/favorites/TrackFavoriteIcon'
+import TrackPlaylistIcon from '@/components/playlists/TrackPlaylistIcon'
 import Modal from '@/components/semantic/Modal'
-import EmbedWizard from "@/components/audio/EmbedWizard"
+import EmbedWizard from '@/components/audio/EmbedWizard'
 import ReportMixin from '@/components/mixins/Report'
-import {momentFormat} from '@/filters'
-
-const FETCH_URL = "tracks/"
+import { momentFormat } from '@/filters'
 
+const FETCH_URL = 'tracks/'
 
-
-function escapeHtml(unsafe) {
+function escapeHtml (unsafe) {
   return unsafe
-        .replace(/&/g, "&amp;")
-        .replace(/</g, "&lt;")
-        .replace(/>/g, "&gt;")
-        .replace(/"/g, "&quot;")
-        .replace(/'/g, "&#039;");
+    .replace(/&/g, '&amp;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    .replace(/"/g, '&quot;')
+    .replace(/'/g, '&#039;')
 }
 
-
 export default {
-  props: ["id"],
-  mixins: [ReportMixin],
   components: {
     PlayButton,
     TrackPlaylistIcon,
     TrackFavoriteIcon,
     Modal,
-    EmbedWizard,
+    EmbedWizard
   },
-  data() {
+  mixins: [ReportMixin],
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       time,
       isLoading: true,
@@ -168,41 +263,12 @@ export default {
       libraries: []
     }
   },
-  created() {
-    this.fetchData()
-  },
-  methods: {
-    fetchData() {
-      var self = this
-      this.isLoading = true
-      let url = FETCH_URL + this.id + "/"
-      logger.default.debug('Fetching track "' + this.id + '"')
-      axios.get(url, {params: {refresh: 'true'}}).then(response => {
-        self.track = response.data
-        axios.get(`artists/${response.data.artist.id}/`).then(response => {
-          self.artist = response.data
-        })
-        self.isLoading = false
-      })
-    },
-    remove () {
-      let self = this
-      self.isLoading = true
-      axios.delete(`tracks/${this.track.id}`).then((response) => {
-        self.isLoading = false
-        self.$emit('deleted')
-        self.$router.push({name: 'library.artists.detail', params: {id: this.artist.id}})
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    }
-  },
   computed: {
     domain () {
       if (this.track) {
         return getDomain(this.track.fid)
       }
+      return null
     },
     publicLibraries () {
       return this.libraries.filter(l => {
@@ -210,49 +276,50 @@ export default {
       })
     },
     isEmbedable () {
-      let self = this
-      return self.artist && self.artist.channel && self.artist.channel.actor || this.publicLibraries.length > 0
-		},
-    upload() {
+      const self = this
+      return (self.artist && self.artist.channel && self.artist.channel.actor) || this.publicLibraries.length > 0
+    },
+    upload () {
       if (this.track.uploads) {
         return this.track.uploads[0]
       }
+      return null
     },
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext('*/*/*/Noun', "Track"),
-        download: this.$pgettext('Content/Track/Link/Verb', "Download"),
-        more: this.$pgettext('*/*/Button.Label/Noun', "More…"),
+        title: this.$pgettext('*/*/*/Noun', 'Track'),
+        download: this.$pgettext('Content/Track/Link/Verb', 'Download'),
+        more: this.$pgettext('*/*/Button.Label/Noun', 'More…')
       }
     },
-    wikipediaUrl() {
+    wikipediaUrl () {
       return (
-        "https://en.wikipedia.org/w/index.php?search=" +
-        encodeURI(this.track.title + " " + this.track.artist.name)
+        'https://en.wikipedia.org/w/index.php?search=' +
+        encodeURI(this.track.title + ' ' + this.track.artist.name)
       )
     },
-    discogsUrl() {
+    discogsUrl () {
       if (this.track.album) {
         return (
-          "https://discogs.com/search/?type=release&title=" +
-    encodeURI(this.track.album.title) + "&artist=" +
-    encodeURI(this.track.artist.name) + "&track=" +
+          'https://discogs.com/search/?type=release&title=' +
+    encodeURI(this.track.album.title) + '&artist=' +
+    encodeURI(this.track.artist.name) + '&track=' +
     encodeURI(this.track.title)
         )
-
       }
+      return null
     },
-    downloadUrl() {
-      let u = this.$store.getters["instance/absoluteUrl"](
+    downloadUrl () {
+      let u = this.$store.getters['instance/absoluteUrl'](
         this.upload.listen_url
       )
       if (this.$store.state.auth.authenticated) {
-        let param = "jwt"
+        let param = 'jwt'
         let value = this.$store.state.auth.token
         if (this.$store.state.auth.scopedTokens && this.$store.state.auth.scopedTokens.listen) {
           // used scoped tokens instead of JWT to reduce the attack surface if the token
           // is leaked
-          param = "token"
+          param = 'token'
           value = this.$store.state.auth.scopedTokens.listen
         }
         u = url.updateQueryString(
@@ -264,7 +331,7 @@ export default {
       return u
     },
     attributedToUrl () {
-      let route = this.$router.resolve({
+      const route = this.$router.resolve({
         name: 'profile.full.overview',
         params: {
           username: this.track.attributed_to.preferred_username,
@@ -274,21 +341,21 @@ export default {
       return route.href
     },
     albumUrl () {
-      let route = this.$router.resolve({name: 'library.albums.detail', params: {id: this.track.album.id }})
+      const route = this.$router.resolve({ name: 'library.albums.detail', params: { id: this.track.album.id } })
       return route.href
     },
     artistUrl () {
-      let route = this.$router.resolve({name: 'library.artists.detail', params: {id: this.track.artist.id }})
+      const route = this.$router.resolve({ name: 'library.artists.detail', params: { id: this.track.artist.id } })
       return route.href
     },
-    headerStyle() {
+    headerStyle () {
       if (!this.cover || !this.cover.urls.original) {
-        return ""
+        return ''
       }
       return (
-        "background-image: url(" +
-        this.$store.getters["instance/absoluteUrl"](this.cover.urls.original) +
-        ")"
+        'background-image: url(' +
+        this.$store.getters['instance/absoluteUrl'](this.cover.urls.original) +
+        ')'
       )
     },
     subtitle () {
@@ -299,21 +366,51 @@ export default {
           uploaderUrl: this.attributedToUrl,
           uploader: escapeHtml(`@${this.track.attributed_to.full_username}`),
           date: escapeHtml(this.track.creation_date),
-          prettyDate: escapeHtml(momentFormat(this.track.creation_date, 'LL')),
+          prettyDate: escapeHtml(momentFormat(this.track.creation_date, 'LL'))
         })
       } else {
         msg = this.$pgettext('Content/Track/Paragraph', 'Uploaded on <time title="%{ date }" datetime="%{ date }">%{ prettyDate }</time>')
         return this.$gettextInterpolate(msg, {
           date: escapeHtml(this.track.creation_date),
-          prettyDate: escapeHtml(momentFormat(this.track.creation_date, 'LL')),
+          prettyDate: escapeHtml(momentFormat(this.track.creation_date, 'LL'))
         })
       }
     }
   },
   watch: {
-    id() {
+    id () {
       this.fetchData()
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      const self = this
+      this.isLoading = true
+      const url = FETCH_URL + this.id + '/'
+      logger.default.debug('Fetching track "' + this.id + '"')
+      axios.get(url, { params: { refresh: 'true' } }).then(response => {
+        self.track = response.data
+        axios.get(`artists/${response.data.artist.id}/`).then(response => {
+          self.artist = response.data
+        })
+        self.isLoading = false
+      })
     },
+    remove () {
+      const self = this
+      self.isLoading = true
+      axios.delete(`tracks/${this.track.id}`).then((response) => {
+        self.isLoading = false
+        self.$emit('deleted')
+        self.$router.push({ name: 'library.artists.detail', params: { id: this.artist.id } })
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    }
   }
 }
 </script>
diff --git a/front/src/components/library/TrackDetail.vue b/front/src/components/library/TrackDetail.vue
index 3d54a53a7..182eba921 100644
--- a/front/src/components/library/TrackDetail.vue
+++ b/front/src/components/library/TrackDetail.vue
@@ -1,57 +1,116 @@
 <template>
-
   <div v-if="track">
     <section class="ui vertical stripe segment">
       <div class="ui stackable grid row container">
         <div class="six wide column">
           <template v-if="upload">
-            <img alt="Cover Image" class="ui fluid image track-cover-image" v-if="track.cover && track.cover.urls.large_square_crop" v-lazy="$store.getters['instance/absoluteUrl'](track.cover.urls.large_square_crop)">
-            <img alt="Cover Image" class="ui fluid image track-cover-image" v-else-if="track.album.cover && track.album.cover.urls.large_square_crop" v-lazy="$store.getters['instance/absoluteUrl'](track.album.cover.urls.large_square_crop)">
+            <img
+              v-if="track.cover && track.cover.urls.large_square_crop"
+              v-lazy="$store.getters['instance/absoluteUrl'](track.cover.urls.large_square_crop)"
+              alt="Cover Image"
+              class="ui fluid image track-cover-image"
+            >
+            <img
+              v-else-if="track.album.cover && track.album.cover.urls.large_square_crop"
+              v-lazy="$store.getters['instance/absoluteUrl'](track.album.cover.urls.large_square_crop)"
+              alt="Cover Image"
+              class="ui fluid image track-cover-image"
+            >
             <h3 class="ui header">
-              <translate key="1" v-if="track.artist.content_category === 'music'" translate-context="Content/*/*">Track Details</translate>
-              <translate key="2" v-else translate-context="Content/*/*">Episode Details</translate>
+              <translate
+                v-if="track.artist.content_category === 'music'"
+                key="1"
+                translate-context="Content/*/*"
+              >
+                Track Details
+              </translate>
+              <translate
+                v-else
+                key="2"
+                translate-context="Content/*/*"
+              >
+                Episode Details
+              </translate>
             </h3>
             <table class="ui basic table">
               <tbody>
                 <tr>
                   <td>
-                    <translate translate-context="Content/*/*">Duration</translate>
+                    <translate translate-context="Content/*/*">
+                      Duration
+                    </translate>
                   </td>
                   <td class="right aligned">
-                    <template v-if="upload.duration">{{ upload.duration | duration }}</template>
-                    <translate v-else translate-context="*/*/*">N/A</translate>
+                    <template v-if="upload.duration">
+                      {{ upload.duration | duration }}
+                    </template>
+                    <translate
+                      v-else
+                      translate-context="*/*/*"
+                    >
+                      N/A
+                    </translate>
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Content/*/*/Noun">Size</translate>
+                    <translate translate-context="Content/*/*/Noun">
+                      Size
+                    </translate>
                   </td>
                   <td class="right aligned">
-                    <template v-if="upload.size">{{ upload.size | humanSize }}</template>
-                    <translate v-else translate-context="*/*/*">N/A</translate>
+                    <template v-if="upload.size">
+                      {{ upload.size | humanSize }}
+                    </template>
+                    <translate
+                      v-else
+                      translate-context="*/*/*"
+                    >
+                      N/A
+                    </translate>
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Content/*/*/Noun">Codec</translate>
+                    <translate translate-context="Content/*/*/Noun">
+                      Codec
+                    </translate>
                   </td>
                   <td class="right aligned">
-                    <template v-if="upload.extension">{{ upload.extension }}</template>
-                    <translate v-else translate-context="*/*/*">N/A</translate>
+                    <template v-if="upload.extension">
+                      {{ upload.extension }}
+                    </template>
+                    <translate
+                      v-else
+                      translate-context="*/*/*"
+                    >
+                      N/A
+                    </translate>
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Content/Track/*/Noun">Bitrate</translate>
+                    <translate translate-context="Content/Track/*/Noun">
+                      Bitrate
+                    </translate>
                   </td>
                   <td class="right aligned">
-                    <template v-if="upload.bitrate">{{ upload.bitrate | humanSize }}/s</template>
-                    <translate v-else translate-context="*/*/*">N/A</translate>
+                    <template v-if="upload.bitrate">
+                      {{ upload.bitrate | humanSize }}/s
+                    </template>
+                    <translate
+                      v-else
+                      translate-context="*/*/*"
+                    >
+                      N/A
+                    </translate>
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Content/*/*">Downloads</translate>
+                    <translate translate-context="Content/*/*">
+                      Downloads
+                    </translate>
                   </td>
                   <td class="right aligned">
                     {{ track.downloads_count }}
@@ -59,26 +118,30 @@
                 </tr>
               </tbody>
             </table>
-
           </template>
         </div>
         <div class="ten wide column">
           <template v-if="track.tags && track.tags.length > 0">
-            <tags-list :tags="track.tags"></tags-list>
-            <div class="ui hidden divider"></div>
+            <tags-list :tags="track.tags" />
+            <div class="ui hidden divider" />
           </template>
 
           <rendered-description
             :content="track.description"
-            :can-update="false"></rendered-description>
+            :can-update="false"
+          />
           <h2 class="ui header">
-            <translate translate-context="Content/*/*">Release Details</translate>
+            <translate translate-context="Content/*/*">
+              Release Details
+            </translate>
           </h2>
           <table class="ui basic table ellipsis-rows">
             <tbody>
               <tr>
                 <td>
-                  <translate translate-context="*/*/*/Noun">Artist</translate>
+                  <translate translate-context="*/*/*/Noun">
+                    Artist
+                  </translate>
                 </td>
                 <td class="right aligned">
                   <router-link :to="{name: 'library.artists.detail', params: {id: track.artist.id}}">
@@ -88,8 +151,20 @@
               </tr>
               <tr v-if="track.album">
                 <td>
-                  <translate key="1" v-if="track.album.artist.content_category === 'music'" translate-context="*/*/*/Noun">Album</translate>
-                  <translate key="2" v-else translate-context="*/*/*">Serie</translate>
+                  <translate
+                    v-if="track.album.artist.content_category === 'music'"
+                    key="1"
+                    translate-context="*/*/*/Noun"
+                  >
+                    Album
+                  </translate>
+                  <translate
+                    v-else
+                    key="2"
+                    translate-context="*/*/*"
+                  >
+                    Serie
+                  </translate>
                 </td>
                 <td class="right aligned">
                   <router-link :to="{name: 'library.albums.detail', params: {id: track.album.id}}">
@@ -99,64 +174,112 @@
               </tr>
               <tr>
                 <td>
-                  <translate translate-context="*/*/*">Year</translate>
+                  <translate translate-context="*/*/*">
+                    Year
+                  </translate>
                 </td>
                 <td class="right aligned">
                   <template v-if="track.album && track.album.release_date">
                     {{ track.album.release_date | moment('Y') }}
                   </template>
                   <template v-else>
-                    <translate translate-context="*/*/*">N/A</translate>
+                    <translate translate-context="*/*/*">
+                      N/A
+                    </translate>
                   </template>
                 </td>
               </tr>
               <tr>
                 <td>
-                  <translate translate-context="Content/Track/*/Noun">Copyright</translate>
+                  <translate translate-context="Content/Track/*/Noun">
+                    Copyright
+                  </translate>
                 </td>
                 <td class="right aligned">
-                  <span v-if="track.copyright" :title="track.copyright">{{ track.copyright|truncate(50) }}</span>
+                  <span
+                    v-if="track.copyright"
+                    :title="track.copyright"
+                  >{{ track.copyright|truncate(50) }}</span>
                   <template v-else>
-                    <translate translate-context="*/*/*">N/A</translate>
+                    <translate translate-context="*/*/*">
+                      N/A
+                    </translate>
                   </template>
                 </td>
               </tr>
               <tr>
                 <td>
-                  <translate translate-context="Content/*/*/Noun">License</translate>
+                  <translate translate-context="Content/*/*/Noun">
+                    License
+                  </translate>
                 </td>
                 <td class="right aligned">
-                  <a v-if="license" :href="license.url" target="_blank" rel="noopener noreferrer">{{ license.name }}</a>
-                  <translate v-else translate-context="*/*/*">N/A</translate>
+                  <a
+                    v-if="license"
+                    :href="license.url"
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >{{ license.name }}</a>
+                  <translate
+                    v-else
+                    translate-context="*/*/*"
+                  >
+                    N/A
+                  </translate>
                 </td>
               </tr>
               <tr v-if="!track.is_local">
                 <td>
-                  <translate translate-context="Content/*/*/Noun">URL</translate>
+                  <translate translate-context="Content/*/*/Noun">
+                    URL
+                  </translate>
                 </td>
                 <td :title="track.fid">
-                  <a :href="track.fid" target="_blank" rel="noopener noreferrer">
-                    {{ track.fid|truncate(65)}}
+                  <a
+                    :href="track.fid"
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >
+                    {{ track.fid|truncate(65) }}
                   </a>
                 </td>
               </tr>
             </tbody>
           </table>
-          <a v-if="musicbrainzUrl" :href="musicbrainzUrl" target="_blank" rel="noreferrer noopener">
-            <i class="external icon"></i>
+          <a
+            v-if="musicbrainzUrl"
+            :href="musicbrainzUrl"
+            target="_blank"
+            rel="noreferrer noopener"
+          >
+            <i class="external icon" />
             <translate translate-context="Content/*/*/Clickable, Verb">View on MusicBrainz</translate>
           </a>
           <h2 class="ui header">
-            <translate translate-context="Content/*/Title/Noun">Related Playlists</translate>
+            <translate translate-context="Content/*/Title/Noun">
+              Related Playlists
+            </translate>
           </h2>
-          <playlist-widget :url="'playlists/'" :filters="{track: track.id, playable: true, ordering: '-modification_date'}">
-          </playlist-widget>
+          <playlist-widget
+            :url="'playlists/'"
+            :filters="{track: track.id, playable: true, ordering: '-modification_date'}"
+          />
 
           <h2 class="ui header">
-            <translate translate-context="Content/*/Title/Noun">Related Libraries</translate>
+            <translate translate-context="Content/*/Title/Noun">
+              Related Libraries
+            </translate>
           </h2>
-          <library-widget @loaded="$emit('libraries-loaded', $event)" :url="'tracks/' + id + '/libraries/'">
-            <translate translate-context="Content/Track/Paragraph" slot="subtitle">This track is present in the following libraries:</translate>
+          <library-widget
+            :url="'tracks/' + id + '/libraries/'"
+            @loaded="$emit('libraries-loaded', $event)"
+          >
+            <translate
+              slot="subtitle"
+              translate-context="Content/Track/Paragraph"
+            >
+              This track is present in the following libraries:
+            </translate>
           </library-widget>
         </div>
       </div>
@@ -165,59 +288,46 @@
 </template>
 
 <script>
-import axios from "axios"
-import url from "@/utils/url"
-import logger from "@/logging"
-import LibraryWidget from "@/components/federation/LibraryWidget"
-import TagsList from "@/components/tags/List"
-import PlaylistWidget from "@/components/playlists/Widget"
-
-const FETCH_URL = "tracks/"
+import axios from 'axios'
+import LibraryWidget from '@/components/federation/LibraryWidget'
+import TagsList from '@/components/tags/List'
+import PlaylistWidget from '@/components/playlists/Widget'
 
 export default {
-  props: ["track", "libraries"],
   components: {
     LibraryWidget,
     TagsList,
-    PlaylistWidget,
+    PlaylistWidget
+  },
+  props: {
+    track: { type: Object, required: true },
+    libraries: { type: Array, required: true }
   },
-  data() {
+  data () {
     return {
       id: this.track.id,
       licenseData: null
     }
   },
-  created() {
-    if (this.track && this.track.license) {
-      this.fetchLicenseData(this.track.license)
-    }
-  },
-  methods: {
-    fetchLicenseData(licenseId) {
-      var self = this
-      let url = `licenses/${licenseId}`
-      axios.get(url).then(response => {
-        self.licenseData = response.data
-      })
-    },
-  },
   computed: {
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext('*/*/*/Noun', "Track")
+        title: this.$pgettext('*/*/*/Noun', 'Track')
       }
     },
-    musicbrainzUrl() {
+    musicbrainzUrl () {
       if (this.track.mbid) {
-        return "https://musicbrainz.org/recording/" + this.track.mbid
+        return 'https://musicbrainz.org/recording/' + this.track.mbid
       }
+      return null
     },
-    upload() {
+    upload () {
       if (this.track.uploads) {
         return this.track.uploads[0]
       }
+      return null
     },
-    license() {
+    license () {
       if (!this.track || !this.track.license) {
         return null
       }
@@ -230,7 +340,8 @@ export default {
       if (this.track.album && this.track.album.cover) {
         return this.track.album.cover
       }
-    },
+      return null
+    }
   },
   watch: {
     track (v) {
@@ -238,6 +349,20 @@ export default {
         this.fetchLicenseData(v.license)
       }
     }
+  },
+  created () {
+    if (this.track && this.track.license) {
+      this.fetchLicenseData(this.track.license)
+    }
+  },
+  methods: {
+    fetchLicenseData (licenseId) {
+      const self = this
+      const url = `licenses/${licenseId}`
+      axios.get(url).then(response => {
+        self.licenseData = response.data
+      })
+    }
   }
 }
 </script>
diff --git a/front/src/components/library/TrackEdit.vue b/front/src/components/library/TrackEdit.vue
index 18e71e8fa..add5c4485 100644
--- a/front/src/components/library/TrackEdit.vue
+++ b/front/src/components/library/TrackEdit.vue
@@ -1,60 +1,84 @@
 <template>
-
   <section class="ui vertical stripe segment">
     <div class="ui text container">
       <h2>
-        <translate v-if="canEdit" key="1" translate-context="Content/*/Title">Edit this track</translate>
-        <translate v-else key="2" translate-context="Content/*/Title">Suggest an edit on this track</translate>
+        <translate
+          v-if="canEdit"
+          key="1"
+          translate-context="Content/*/Title"
+        >
+          Edit this track
+        </translate>
+        <translate
+          v-else
+          key="2"
+          translate-context="Content/*/Title"
+        >
+          Suggest an edit on this track
+        </translate>
       </h2>
-      <div class="ui message" v-if="!object.is_local">
-        <translate translate-context="Content/*/Message">This object is managed by another server, you cannot edit it.</translate>
+      <div
+        v-if="!object.is_local"
+        class="ui message"
+      >
+        <translate translate-context="Content/*/Message">
+          This object is managed by another server, you cannot edit it.
+        </translate>
       </div>
       <edit-form
         v-else-if="!isLoadingLicenses"
         :object-type="objectType"
         :object="object"
         :can-edit="canEdit"
-        :licenses="licenses"></edit-form>
-      <div v-else class="ui inverted active dimmer">
-        <div class="ui loader"></div>
+        :licenses="licenses"
+      />
+      <div
+        v-else
+        class="ui inverted active dimmer"
+      >
+        <div class="ui loader" />
       </div>
     </div>
   </section>
 </template>
 
 <script>
-import axios from "axios"
+import axios from 'axios'
 
 import EditForm from '@/components/library/EditForm'
 export default {
-  props: ["objectType", "object", "libraries"],
-  data() {
+  components: {
+    EditForm
+  },
+  props: {
+    objectType: { type: String, required: true },
+    object: { type: Object, required: true },
+    libraries: { type: Array, required: true }
+  },
+  data () {
     return {
       id: this.object.id,
       isLoadingLicenses: false,
       licenses: []
     }
   },
-  components: {
-    EditForm
+  computed: {
+    canEdit () {
+      return true
+    }
   },
   created () {
     this.fetchLicenses()
   },
   methods: {
     fetchLicenses () {
-      let self = this
+      const self = this
       self.isLoadingLicenses = true
       axios.get('licenses/').then((response) => {
         self.isLoadingLicenses = false
         self.licenses = response.data.results
       })
     }
-  },
-  computed: {
-    canEdit () {
-      return true
-    }
   }
 }
 </script>
diff --git a/front/src/components/library/UploadDetail.vue b/front/src/components/library/UploadDetail.vue
index ce3a1e3a8..89d4a4cce 100644
--- a/front/src/components/library/UploadDetail.vue
+++ b/front/src/components/library/UploadDetail.vue
@@ -1,28 +1,30 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment">
-      <div class="ui centered active inline loader"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div class="ui centered active inline loader" />
     </div>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-
+import axios from 'axios'
 
 export default {
-  props: ["id"],
-  async created() {
-    let upload = await this.fetchData()
-    this.$router.replace({name: "library.tracks.detail", params: {id: upload.track.id}})
+  props: { id: { type: Number, required: true } },
+  async created () {
+    const upload = await this.fetchData()
+    this.$router.replace({ name: 'library.tracks.detail', params: { id: upload.track.id } })
   },
   methods: {
-    async fetchData() {
+    async fetchData () {
       this.isLoading = true
-      let response = await axios.get(`uploads/${this.id}/`, {params: {refresh: 'true', include_channels: 'true'}})
+      const response = await axios.get(`uploads/${this.id}/`, { params: { refresh: 'true', include_channels: 'true' } })
       this.isLoading = false
       return response.data
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/library/radios/Builder.vue b/front/src/components/library/radios/Builder.vue
index 5a29708b4..a2853c4f2 100644
--- a/front/src/components/library/radios/Builder.vue
+++ b/front/src/components/library/radios/Builder.vue
@@ -1,53 +1,117 @@
 <template>
-  <div class="ui vertical stripe segment" v-title="labels.title">
+  <div
+    v-title="labels.title"
+    class="ui vertical stripe segment"
+  >
     <div>
       <section>
         <h2 class="ui header">
-          <translate translate-context="Content/Radio/Title">Builder</translate>
+          <translate translate-context="Content/Radio/Title">
+            Builder
+          </translate>
         </h2>
-        <p><translate translate-context="Content/Radio/Paragraph">You can use this interface to build your own custom radio, which will play tracks according to your criteria.</translate></p>
+        <p>
+          <translate translate-context="Content/Radio/Paragraph">
+            You can use this interface to build your own custom radio, which will play tracks according to your criteria.
+          </translate>
+        </p>
         <div class="ui form">
-          <div v-if="success" class="ui positive message">
+          <div
+            v-if="success"
+            class="ui positive message"
+          >
             <h4 class="header">
               <template v-if="radioName">
-                <translate translate-context="Content/Radio/Message">Radio updated</translate>
+                <translate translate-context="Content/Radio/Message">
+                  Radio updated
+                </translate>
               </template>
               <template v-else>
-                <translate translate-context="Content/Radio/Message">Radio created</translate>
+                <translate translate-context="Content/Radio/Message">
+                  Radio created
+                </translate>
               </template>
             </h4>
           </div>
           <div class="">
             <div class="field">
               <label for="name"><translate translate-context="Content/Radio/Input.Label/Noun">Radio name</translate></label>
-              <input id="name" name="name" type="text" v-model="radioName" :placeholder="labels.placeholder.name" />
+              <input
+                id="name"
+                v-model="radioName"
+                name="name"
+                type="text"
+                :placeholder="labels.placeholder.name"
+              >
             </div>
             <div class="field">
               <label for="description"><translate translate-context="*/*/*/Noun">Description</translate></label>
-              <textarea rows="2" id="description" type="text" v-model="radioDesc" :placeholder="labels.placeholder.description" />
+              <textarea
+                id="description"
+                v-model="radioDesc"
+                rows="2"
+                type="text"
+                :placeholder="labels.placeholder.description"
+              />
             </div>
             <div class="ui toggle checkbox">
-              <input id="public" type="checkbox" v-model="isPublic" />
+              <input
+                id="public"
+                v-model="isPublic"
+                type="checkbox"
+              >
               <label for="public"><translate translate-context="Content/Radio/Checkbox.Label/Verb">Display publicly</translate></label>
             </div>
-            <div class="ui hidden divider"></div>
-            <button :disabled="!canSave" @click="save" :class="['ui', 'success', {loading: isLoading}, 'button']">
-              <translate translate-context="Content/*/Button.Label/Verb">Save</translate>
+            <div class="ui hidden divider" />
+            <button
+              :disabled="!canSave"
+              :class="['ui', 'success', {loading: isLoading}, 'button']"
+              @click="save"
+            >
+              <translate translate-context="Content/*/Button.Label/Verb">
+                Save
+              </translate>
             </button>
-            <radio-button v-if="id" type="custom" :custom-radio-id="id"></radio-button>
+            <radio-button
+              v-if="id"
+              type="custom"
+              :custom-radio-id="id"
+            />
           </div>
         </div>
         <div class="ui form">
           <div class="inline field">
-            <label id="radioFilterLabel" for="radio-filters"><translate translate-context="Content/Radio/Paragraph">Add filters to customize your radio</translate></label>
-            <select id="radio-filters" class="ui dropdown" v-model="currentFilterType">
+            <label
+              id="radioFilterLabel"
+              for="radio-filters"
+            ><translate translate-context="Content/Radio/Paragraph">Add filters to customize your radio</translate></label>
+            <select
+              id="radio-filters"
+              v-model="currentFilterType"
+              class="ui dropdown"
+            >
               <option value="">
-                <translate translate-context="Content/Radio/Dropdown.Placeholder/Verb">Select a filter</translate>
+                <translate translate-context="Content/Radio/Dropdown.Placeholder/Verb">
+                  Select a filter
+                </translate>
+              </option>
+              <option
+                v-for="(f, key) in availableFilters"
+                :key="key"
+                :value="f.type"
+              >
+                {{ f.label }}
               </option>
-              <option v-for="f in availableFilters" :value="f.type">{{ f.label }}</option>
             </select>
-            <button id="addFilter" :disabled="!currentFilterType" @click="add" class="ui button">
-              <translate translate-context="Content/Radio/Button.Label/Verb">Add filter</translate>
+            <button
+              id="addFilter"
+              :disabled="!currentFilterType"
+              class="ui button"
+              @click="add"
+            >
+              <translate translate-context="Content/Radio/Button.Label/Verb">
+                Add filter
+              </translate>
             </button>
           </div>
           <p v-if="currentFilter">
@@ -57,11 +121,31 @@
         <table class="ui table">
           <thead>
             <tr>
-              <th class="two wide"><translate translate-context="Content/Radio/Table.Label/Noun">Filter name</translate></th>
-              <th class="one wide"><translate translate-context="Content/Radio/Table.Label/Verb">Exclude</translate></th>
-              <th class="six wide"><translate translate-context="Content/Radio/Table.Label/Verb (Value is a List of Parameters)">Config</translate></th>
-              <th class="five wide"><translate translate-context="Content/Radio/Table.Label/Noun (Value is a number of Tracks)">Candidates</translate></th>
-              <th class="two wide"><translate translate-context="Content/*/*/Noun">Actions</translate></th>
+              <th class="two wide">
+                <translate translate-context="Content/Radio/Table.Label/Noun">
+                  Filter name
+                </translate>
+              </th>
+              <th class="one wide">
+                <translate translate-context="Content/Radio/Table.Label/Verb">
+                  Exclude
+                </translate>
+              </th>
+              <th class="six wide">
+                <translate translate-context="Content/Radio/Table.Label/Verb (Value is a List of Parameters)">
+                  Config
+                </translate>
+              </th>
+              <th class="five wide">
+                <translate translate-context="Content/Radio/Table.Label/Noun (Value is a number of Tracks)">
+                  Candidates
+                </translate>
+              </th>
+              <th class="two wide">
+                <translate translate-context="Content/*/*/Noun">
+                  Actions
+                </translate>
+              </th>
             </tr>
           </thead>
           <tbody>
@@ -69,52 +153,54 @@
               v-for="(f, index) in filters"
               :key="(f, index, f.hash)"
               :index="index"
+              :config="f.config"
+              :filter="f.filter"
               @update-config="updateConfig"
               @delete="deleteFilter"
-              :config="f.config"
-              :filter="f.filter">
-            </builder-filter>
+            />
           </tbody>
         </table>
         <template v-if="checkResult && checkResult.candidates && checkResult.candidates.count">
           <h3
-            class="ui header"
             v-translate="{count: checkResult.candidates.count}"
+            class="ui header"
             :translate-n="checkResult.candidates.count"
             translate-plural="%{ count } tracks matching combined filters"
-            translate-context="Content/Radio/Table.Paragraph/Short">
+            translate-context="Content/Radio/Table.Paragraph/Short"
+          >
             %{ count } track matching combined filters
           </h3>
-          <track-table 
-            v-if="checkResult.candidates.sample" 
-            :tracks="checkResult.candidates.sample" 
+          <track-table
+            v-if="checkResult.candidates.sample"
+            :tracks="checkResult.candidates.sample"
             :playable="true"
             :show-position="false"
             :show-duration="false"
-            :display-actions="false"></track-table>
+            :display-actions="false"
+          />
         </template>
       </section>
     </div>
   </div>
 </template>
 <script>
-import axios from "axios"
-import $ from "jquery"
-import _ from "@/lodash"
-import BuilderFilter from "./Filter"
-import TrackTable from "@/components/audio/track/Table"
-import RadioButton from "@/components/radios/Button"
+import axios from 'axios'
+import $ from 'jquery'
+import _ from '@/lodash'
+import BuilderFilter from './Filter'
+import TrackTable from '@/components/audio/track/Table'
+import RadioButton from '@/components/radios/Button'
 
 export default {
-  props: {
-    id: { required: false }
-  },
   components: {
     BuilderFilter,
     TrackTable,
     RadioButton
   },
-  data: function() {
+  props: {
+    id: { type: Number, required: false, default: 0 }
+  },
+  data: function () {
     return {
       isLoading: false,
       success: false,
@@ -122,31 +208,68 @@ export default {
       currentFilterType: null,
       filters: [],
       checkResult: null,
-      radioName: "",
-      radioDesc: "",
+      radioName: '',
+      radioDesc: '',
       isPublic: true
     }
   },
-  created: function() {
-    let self = this
+  computed: {
+    labels () {
+      const title = this.$pgettext('Head/Radio/Title', 'Radio Builder')
+      const placeholder = {
+        name: this.$pgettext('Content/Radio/Input.Placeholder', 'My awesome radio'),
+        description: this.$pgettext('Content/Radio/Input.Placeholder', 'My awesome description')
+      }
+      return {
+        title,
+        placeholder
+      }
+    },
+    canSave: function () {
+      return this.radioName.length > 0 && this.checkErrors.length === 0
+    },
+    checkErrors: function () {
+      if (!this.checkResult) {
+        return []
+      }
+      const errors = this.checkResult.errors
+      return errors
+    },
+    currentFilter: function () {
+      const self = this
+      return this.availableFilters.filter(e => {
+        return e.type === self.currentFilterType
+      })[0]
+    }
+  },
+  watch: {
+    filters: {
+      handler: function () {
+        this.fetchCandidates()
+      },
+      deep: true
+    }
+  },
+  created: function () {
+    const self = this
     this.fetchFilters().then(() => {
       if (self.id) {
         self.fetch()
       }
     })
   },
-  mounted() {
-    $(".ui.dropdown").dropdown()
+  mounted () {
+    $('.ui.dropdown').dropdown()
   },
   methods: {
-    fetchFilters: function() {
-      let self = this
-      let url = "radios/radios/filters/"
+    fetchFilters: function () {
+      const self = this
+      const url = 'radios/radios/filters/'
       return axios.get(url).then(response => {
         self.availableFilters = response.data
       })
     },
-    add() {
+    add () {
       this.filters.push({
         config: {},
         filter: this.currentFilter,
@@ -154,18 +277,18 @@ export default {
       })
       this.fetchCandidates()
     },
-    updateConfig(index, field, value) {
+    updateConfig (index, field, value) {
       this.filters[index].config[field] = value
       this.fetchCandidates()
     },
-    deleteFilter(index) {
+    deleteFilter (index) {
       this.filters.splice(index, 1)
       this.fetchCandidates()
     },
-    fetch: function() {
-      let self = this
+    fetch: function () {
+      const self = this
       self.isLoading = true
-      let url = "radios/radios/" + this.id + "/"
+      const url = 'radios/radios/' + this.id + '/'
       axios.get(url).then(response => {
         self.filters = response.data.config.map(f => {
           return {
@@ -182,28 +305,28 @@ export default {
         self.isLoading = false
       })
     },
-    fetchCandidates: function() {
-      let self = this
-      let url = "radios/radios/validate/"
+    fetchCandidates: function () {
+      const self = this
+      const url = 'radios/radios/validate/'
       let final = this.filters.map(f => {
-        let c = _.clone(f.config)
+        const c = _.clone(f.config)
         c.type = f.filter.type
         return c
       })
       final = {
-        filters: [{ type: "group", filters: final }]
+        filters: [{ type: 'group', filters: final }]
       }
       axios.post(url, final).then(response => {
         self.checkResult = response.data.filters[0]
       })
     },
-    save: function() {
-      let self = this
+    save: function () {
+      const self = this
       self.success = false
       self.isLoading = true
 
       let final = this.filters.map(f => {
-        let c = _.clone(f.config)
+        const c = _.clone(f.config)
         c.type = f.filter.type
         return c
       })
@@ -214,18 +337,18 @@ export default {
         config: final
       }
       if (this.id) {
-        let url = "radios/radios/" + this.id + "/"
+        const url = 'radios/radios/' + this.id + '/'
         axios.put(url, final).then(response => {
           self.isLoading = false
           self.success = true
         })
       } else {
-        let url = "radios/radios/"
+        const url = 'radios/radios/'
         axios.post(url, final).then(response => {
           self.success = true
           self.isLoading = false
           self.$router.push({
-            name: "library.radios.detail",
+            name: 'library.radios.detail',
             params: {
               id: response.data.id
             }
@@ -233,43 +356,6 @@ export default {
         })
       }
     }
-  },
-  computed: {
-    labels() {
-      let title = this.$pgettext('Head/Radio/Title', "Radio Builder")
-      let placeholder = {
-        name: this.$pgettext('Content/Radio/Input.Placeholder', "My awesome radio"),
-        description: this.$pgettext('Content/Radio/Input.Placeholder', "My awesome description")
-      }
-      return {
-        title,
-        placeholder
-      }
-    },
-    canSave: function() {
-      return this.radioName.length > 0 && this.checkErrors.length === 0
-    },
-    checkErrors: function() {
-      if (!this.checkResult) {
-        return []
-      }
-      let errors = this.checkResult.errors
-      return errors
-    },
-    currentFilter: function() {
-      let self = this
-      return this.availableFilters.filter(e => {
-        return e.type === self.currentFilterType
-      })[0]
-    }
-  },
-  watch: {
-    filters: {
-      handler: function() {
-        this.fetchCandidates()
-      },
-      deep: true
-    }
   }
 }
 </script>
diff --git a/front/src/components/library/radios/Filter.vue b/front/src/components/library/radios/Filter.vue
index 1c7fe286b..04b3bfc5a 100644
--- a/front/src/components/library/radios/Filter.vue
+++ b/front/src/components/library/radios/Filter.vue
@@ -3,32 +3,55 @@
     <td>{{ filter.label }}</td>
     <td>
       <div class="ui toggle checkbox">
-        <input id="exclude-filter" name="public" type="checkbox" v-model="exclude" @change="$emit('update-config', index, 'not', exclude)">
-        <label for="exclude-filter" class="visually-hidden">
+        <input
+          id="exclude-filter"
+          v-model="exclude"
+          name="public"
+          type="checkbox"
+          @change="$emit('update-config', index, 'not', exclude)"
+        >
+        <label
+          for="exclude-filter"
+          class="visually-hidden"
+        >
           <translate translate-context="Popup/Radio/Title/Noun">Exclude</translate>
         </label>
       </div>
     </td>
     <td>
       <div
-        v-for="(f, index) in filter.fields"
+        v-for="f in filter.fields"
+        :key="f.name"
+        :ref="f.name"
         class="ui field"
-        :key="(f.name, index)"
-        :ref="f.name">
+      >
         <div :class="['ui', 'search', 'selection', 'dropdown', {'autocomplete': f.autocomplete}, {'multiple': f.type === 'list'}]">
-          <i class="dropdown icon"></i>
-          <div class="default text">{{ f.placeholder }}</div>
-          <input :id="f.name" v-if="f.type === 'list' && config[f.name]" :value="config[f.name].join(',')" type="hidden">
-          <div v-if="config[f.name]" class="ui menu">
+          <i class="dropdown icon" />
+          <div class="default text">
+            {{ f.placeholder }}
+          </div>
+          <input
+            v-if="f.type === 'list' && config[f.name]"
+            :id="f.name"
+            :value="config[f.name].join(',')"
+            type="hidden"
+          >
+          <div
+            v-if="config[f.name]"
+            class="ui menu"
+          >
             <div
-              v-for="(v, index) in config[f.name]"
+              v-for="v in config[f.name]"
+              :key="v"
               class="ui item"
               :data-value="v"
-              :key="v">
-                <template v-if="config.names">
-                  {{ config.names[index] }}
-                </template>
-                <template v-else>{{ v }}</template>
+            >
+              <template v-if="config.names">
+                {{ config.names[index] }}
+              </template>
+              <template v-else>
+                {{ v }}
+              </template>
             </div>
           </div>
         </div>
@@ -36,30 +59,48 @@
     </td>
     <td>
       <a
+        v-if="checkResult"
         href=""
+        :class="['ui', {'success': checkResult.candidates.count > 10}, 'label']"
         @click.prevent="showCandidadesModal = !showCandidadesModal"
-        v-if="checkResult"
-        :class="['ui', {'success': checkResult.candidates.count > 10}, 'label']">
+      >
         {{ checkResult.candidates.count }} tracks matching filter
       </a>
-      <modal v-if="checkResult" :show.sync="showCandidadesModal">
+      <modal
+        v-if="checkResult"
+        :show.sync="showCandidadesModal"
+      >
         <h4 class="header">
-          <translate translate-context="Popup/Radio/Title/Noun">Tracks matching filter</translate>
+          <translate translate-context="Popup/Radio/Title/Noun">
+            Tracks matching filter
+          </translate>
         </h4>
         <div class="content">
           <div class="description">
-            <track-table v-if="checkResult.candidates.count > 0" :tracks="checkResult.candidates.sample"></track-table>
+            <track-table
+              v-if="checkResult.candidates.count > 0"
+              :tracks="checkResult.candidates.sample"
+            />
           </div>
         </div>
         <div class="actions">
           <button class="ui deny button">
-            <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+            <translate translate-context="*/*/Button.Label/Verb">
+              Cancel
+            </translate>
           </button>
         </div>
       </modal>
     </td>
     <td>
-      <button @click="$emit('delete', index)" class="ui danger button"><translate translate-context="Content/Radio/Button.Label/Verb">Remove</translate></button>
+      <button
+        class="ui danger button"
+        @click="$emit('delete', index)"
+      >
+        <translate translate-context="Content/Radio/Button.Label/Verb">
+          Remove
+        </translate>
+      </button>
     </td>
   </tr>
 </template>
@@ -70,18 +111,16 @@ import _ from '@/lodash'
 
 import Modal from '@/components/semantic/Modal'
 import TrackTable from '@/components/audio/track/Table'
-import BuilderFilter from './Filter'
 
 export default {
   components: {
-    BuilderFilter,
     TrackTable,
     Modal
   },
   props: {
-    filter: {type: Object},
-    config: {type: Object},
-    index: {type: Number}
+    filter: { type: Object, required: true },
+    config: { type: Object, required: true },
+    index: { type: Number, required: true }
   },
   data: function () {
     return {
@@ -90,11 +129,16 @@ export default {
       exclude: this.config.not
     }
   },
+  watch: {
+    exclude: function () {
+      this.fetchCandidates()
+    }
+  },
   mounted: function () {
-    let self = this
+    const self = this
     this.filter.fields.forEach(f => {
-      let selector = ['.dropdown']
-      let settings = {
+      const selector = ['.dropdown']
+      const settings = {
         onChange: function (value, text, $choice) {
           value = $(this).dropdown('get value').split(',')
           if (f.type === 'list' && f.subtype === 'number') {
@@ -126,7 +170,7 @@ export default {
             if (settings.fields.remoteValues) {
               return initialResponse
             }
-            return {results: initialResponse.results}
+            return { results: initialResponse.results }
           }
         }
       }
@@ -135,20 +179,15 @@ export default {
   },
   methods: {
     fetchCandidates: function () {
-      let self = this
-      let url = 'radios/radios/validate/'
+      const self = this
+      const url = 'radios/radios/validate/'
       let final = _.clone(this.config)
       final.type = this.filter.type
-      final = {'filters': [final]}
+      final = { filters: [final] }
       axios.post(url, final).then((response) => {
         self.checkResult = response.data.filters[0]
       })
     }
-  },
-  watch: {
-    exclude: function () {
-      this.fetchCandidates()
-    }
   }
 }
 </script>
diff --git a/front/src/components/manage/ChannelsTable.vue b/front/src/components/manage/ChannelsTable.vue
index 337ecfbfe..129860c1e 100644
--- a/front/src/components/manage/ChannelsTable.vue
+++ b/front/src/components/manage/ChannelsTable.vue
@@ -5,75 +5,163 @@
         <div class="ui six wide field">
           <label for="channel-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
           <form @submit.prevent="search.query = $refs.search.value">
-            <input id="channel-search" name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" />
+            <input
+              id="channel-search"
+              ref="search"
+              name="search"
+              type="text"
+              :value="search.query"
+              :placeholder="labels.searchPlaceholder"
+            >
           </form>
         </div>
         <div class="field">
           <label for="channel-category"><translate translate-context="*/*/*">Category</translate></label>
-          <select id="channel-category" class="ui dropdown" @change="addSearchToken('category', $event.target.value)" :value="getTokenValue('category', '')">
-            <option value=""><translate translate-context="Content/*/Dropdown">All</translate></option>
-            <option value="podcast">{{ sharedLabels.fields.content_category.choices.podcast }}</option>
-            <option value="music">{{ sharedLabels.fields.content_category.choices.music }}</option>
-            <option value="other">{{ sharedLabels.fields.content_category.choices.other }}</option>
+          <select
+            id="channel-category"
+            class="ui dropdown"
+            :value="getTokenValue('category', '')"
+            @change="addSearchToken('category', $event.target.value)"
+          >
+            <option value="">
+              <translate translate-context="Content/*/Dropdown">
+                All
+              </translate>
+            </option>
+            <option value="podcast">
+              {{ sharedLabels.fields.content_category.choices.podcast }}
+            </option>
+            <option value="music">
+              {{ sharedLabels.fields.content_category.choices.music }}
+            </option>
+            <option value="other">
+              {{ sharedLabels.fields.content_category.choices.other }}
+            </option>
           </select>
         </div>
         <div class="field">
           <label for="channel-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-          <select id="channel-ordering" class="ui dropdown" v-model="ordering">
-            <option v-for="option in orderingOptions" :value="option[0]">
+          <select
+            id="channel-ordering"
+            v-model="ordering"
+            class="ui dropdown"
+          >
+            <option
+              v-for="(option, key) in orderingOptions"
+              :key="key"
+              :value="option[0]"
+            >
               {{ sharedLabels.filters[option[1]] }}
             </option>
           </select>
         </div>
         <div class="field">
           <label for="channel-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
-          <select id="channel-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-            <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-            <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+          <select
+            id="channel-ordering-direction"
+            v-model="orderingDirection"
+            class="ui dropdown"
+          >
+            <option value="+">
+              <translate translate-context="Content/Search/Dropdown">
+                Ascending
+              </translate>
+            </option>
+            <option value="-">
+              <translate translate-context="Content/Search/Dropdown">
+                Descending
+              </translate>
+            </option>
           </select>
         </div>
       </div>
-      </div>
+    </div>
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-          <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <action-table
         v-if="result"
-        @action-launched="fetchData"
         :objects-data="result"
         :actions="actions"
         action-url="manage/library/artists/action/"
-        :filters="actionFilters">
+        :filters="actionFilters"
+        @action-launched="fetchData"
+      >
         <template slot="header-cells">
-          <th><translate translate-context="*/*/*/Noun">Name</translate></th>
-          <th><translate translate-context="*/*/*/Noun">Account</translate></th>
-          <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th>
-          <th><translate translate-context="*/*/*">Albums</translate></th>
-          <th><translate translate-context="*/*/*">Tracks</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Name
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Account
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/*/Noun">
+              Domain
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Albums
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Tracks
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Creation date
+            </translate>
+          </th>
         </template>
-        <template slot="row-cells" slot-scope="scope">
+        <template
+          slot="row-cells"
+          slot-scope="scope"
+        >
           <td>
-            <router-link :to="{name: 'manage.channels.detail', params: {id: scope.obj.actor.full_username }}">{{ scope.obj.artist.name }}</router-link>
+            <router-link :to="{name: 'manage.channels.detail', params: {id: scope.obj.actor.full_username }}">
+              {{ scope.obj.artist.name }}
+            </router-link>
           </td>
           <td>
             <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: scope.obj.attributed_to.full_username }}">
-              <i class="wrench icon"></i>
+              <i class="wrench icon" />
               <span class="visually-hidden">{{ labels.openModeration }}</span>
             </router-link>
-            <a href="" class="discrete link" @click.prevent="addSearchToken('account', scope.obj.attributed_to.full_username)">{{ scope.obj.attributed_to.preferred_username }}</a>
+            <a
+              href=""
+              class="discrete link"
+              @click.prevent="addSearchToken('account', scope.obj.attributed_to.full_username)"
+            >{{ scope.obj.attributed_to.preferred_username }}</a>
           </td>
           <td>
             <template v-if="!scope.obj.is_local">
               <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.attributed_to.domain }}">
-                <i class="wrench icon"></i>
+                <i class="wrench icon" />
                 <span class="visually-hidden">{{ labels.openModeration }}</span>
               </router-link>
-              <a href="" class="discrete link" @click.prevent="addSearchToken('domain', scope.obj.attributed_to.domain)">{{ scope.obj.attributed_to.domain }}</a>
+              <a
+                href=""
+                class="discrete link"
+                @click.prevent="addSearchToken('domain', scope.obj.attributed_to.domain)"
+              >{{ scope.obj.attributed_to.domain }}</a>
             </template>
-            <a href="" v-else class="ui tiny accent icon link label" @click.prevent="addSearchToken('domain', scope.obj.attributed_to.domain)">
-              <i class="home icon"></i>
+            <a
+              v-else
+              href=""
+              class="ui tiny accent icon link label"
+              @click.prevent="addSearchToken('domain', scope.obj.attributed_to.domain)"
+            >
+              <i class="home icon" />
               <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
             </a>
           </td>
@@ -84,7 +172,7 @@
             {{ scope.obj.artist.tracks_count }}
           </td>
           <td>
-            <human-date :date="scope.obj.creation_date"></human-date>
+            <human-date :date="scope.obj.creation_date" />
           </td>
         </template>
       </action-table>
@@ -92,16 +180,18 @@
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="selectPage"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-        ></pagination>
+        @page-changed="selectPage"
+      />
 
       <span v-if="result && result.results.length > 0">
-        <translate translate-context="Content/*/Paragraph"
-          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
+        <translate
+          translate-context="Content/*/Paragraph"
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
+        >
           Showing results %{ start }-%{ end } on %{ total }
         </translate>
       </span>
@@ -113,25 +203,24 @@
 import axios from 'axios'
 import _ from '@/lodash'
 import time from '@/utils/time'
-import {normalizeQuery, parseTokens} from '@/search'
+import { normalizeQuery, parseTokens } from '@/search'
 import Pagination from '@/components/Pagination'
 import ActionTable from '@/components/common/ActionTable'
 import OrderingMixin from '@/components/mixins/Ordering'
 import TranslationsMixin from '@/components/mixins/Translations'
 import SmartSearchMixin from '@/components/mixins/SmartSearch'
 
-
 export default {
-  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
-  props: {
-    filters: {type: Object, required: false},
-  },
   components: {
     Pagination,
     ActionTable
   },
+  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
+  props: {
+    filters: { type: Object, required: false, default: () => { return {} } }
+  },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
     return {
       time,
       isLoading: false,
@@ -146,36 +235,10 @@ export default {
       ordering: defaultOrdering.field,
       orderingOptions: [
         ['creation_date', 'creation_date'],
-        ["name", "name"],
+        ['name', 'name']
       ]
     }
   },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search.query,
-        'ordering': this.getOrderingAsString()
-      }, this.filters)
-      let self = this
-      self.isLoading = true
-      self.checked = []
-      axios.get('/manage/channels/', {params: params}).then((response) => {
-        self.result = response.data
-        self.isLoading = false
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    },
-    selectPage: function (page) {
-      this.page = page
-    },
-  },
   computed: {
     labels () {
       return {
@@ -184,7 +247,7 @@ export default {
       }
     },
     actionFilters () {
-      var currentFilters = {
+      const currentFilters = {
         q: this.search.query
       }
       if (this.filters) {
@@ -222,6 +285,32 @@ export default {
     orderingDirection () {
       this.fetchData()
     }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search.query,
+        ordering: this.getOrderingAsString()
+      }, this.filters)
+      const self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/channels/', { params: params }).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/library/AlbumsTable.vue b/front/src/components/manage/library/AlbumsTable.vue
index 3bff50352..bdaaf9c68 100644
--- a/front/src/components/manage/library/AlbumsTable.vue
+++ b/front/src/components/manage/library/AlbumsTable.vue
@@ -5,66 +5,139 @@
         <div class="ui six wide field">
           <label for="albums-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
           <form @submit.prevent="search.query = $refs.search.value">
-            <input id="albums-search" name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" />
+            <input
+              id="albums-search"
+              ref="search"
+              name="search"
+              type="text"
+              :value="search.query"
+              :placeholder="labels.searchPlaceholder"
+            >
           </form>
         </div>
         <div class="field">
           <label for="albums-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-          <select id="albums-ordering" class="ui dropdown" v-model="ordering">
-            <option v-for="option in orderingOptions" :value="option[0]">
+          <select
+            id="albums-ordering"
+            v-model="ordering"
+            class="ui dropdown"
+          >
+            <option
+              v-for="(option, key) in orderingOptions"
+              :key="key"
+              :value="option[0]"
+            >
               {{ sharedLabels.filters[option[1]] }}
             </option>
           </select>
         </div>
         <div class="field">
           <label for="albums-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
-          <select class="ui dropdown" v-model="orderingDirection">
-            <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-            <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+          <select
+            v-model="orderingDirection"
+            class="ui dropdown"
+          >
+            <option value="+">
+              <translate translate-context="Content/Search/Dropdown">
+                Ascending
+              </translate>
+            </option>
+            <option value="-">
+              <translate translate-context="Content/Search/Dropdown">
+                Descending
+              </translate>
+            </option>
           </select>
         </div>
       </div>
-      </div>
+    </div>
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-          <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <action-table
         v-if="result"
-        @action-launched="fetchData"
         :objects-data="result"
         :actions="actions"
         action-url="manage/library/albums/action/"
-        :filters="actionFilters">
+        :filters="actionFilters"
+        @action-launched="fetchData"
+      >
         <template slot="header-cells">
-          <th><translate translate-context="*/*/*/Noun">Title</translate></th>
-          <th><translate translate-context="*/*/*/Noun">Artist</translate></th>
-          <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th>
-          <th><translate translate-context="*/*/*">Tracks</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Release date</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Title
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Artist
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/*/Noun">
+              Domain
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Tracks
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Release date
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Creation date
+            </translate>
+          </th>
         </template>
-        <template slot="row-cells" slot-scope="scope">
+        <template
+          slot="row-cells"
+          slot-scope="scope"
+        >
           <td>
-            <router-link :to="{name: 'manage.library.albums.detail', params: {id: scope.obj.id }}">{{ scope.obj.title }}</router-link>
+            <router-link :to="{name: 'manage.library.albums.detail', params: {id: scope.obj.id }}">
+              {{ scope.obj.title }}
+            </router-link>
           </td>
           <td>
             <router-link :to="{name: 'manage.library.artists.detail', params: {id: scope.obj.artist.id }}">
-              <i class="wrench icon"></i>
+              <i class="wrench icon" />
               <span class="visually-hidden">{{ labels.openModeration }}</span>
             </router-link>
-            <a href="" class="discrete link" @click.prevent="addSearchToken('artist', scope.obj.artist.name)" :title="scope.obj.artist.name">{{ scope.obj.artist.name }}</a>
+            <a
+              href=""
+              class="discrete link"
+              :title="scope.obj.artist.name"
+              @click.prevent="addSearchToken('artist', scope.obj.artist.name)"
+            >{{ scope.obj.artist.name }}</a>
           </td>
           <td>
             <template v-if="!scope.obj.is_local">
               <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.domain }}">
-                <i class="wrench icon"></i>
+                <i class="wrench icon" />
                 <span class="visually-hidden">{{ labels.openModeration }}</span>
               </router-link>
-              <a href="" class="discrete link" @click.prevent="addSearchToken('domain', scope.obj.domain)">{{ scope.obj.domain }}</a>
+              <a
+                href=""
+                class="discrete link"
+                @click.prevent="addSearchToken('domain', scope.obj.domain)"
+              >{{ scope.obj.domain }}</a>
             </template>
-            <a href="" v-else class="ui tiny accent icon link label" @click.prevent="addSearchToken('domain', scope.obj.domain)">
-              <i class="home icon"></i>
+            <a
+              v-else
+              href=""
+              class="ui tiny accent icon link label"
+              @click.prevent="addSearchToken('domain', scope.obj.domain)"
+            >
+              <i class="home icon" />
               <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
             </a>
           </td>
@@ -72,12 +145,19 @@
             {{ scope.obj.tracks_count }}
           </td>
           <td>
-            <human-date v-if="scope.obj.release_date" :date="scope.obj.release_date"></human-date>
-            <translate v-else translate-context="*/*/*">N/A</translate>
-
+            <human-date
+              v-if="scope.obj.release_date"
+              :date="scope.obj.release_date"
+            />
+            <translate
+              v-else
+              translate-context="*/*/*"
+            >
+              N/A
+            </translate>
           </td>
           <td>
-            <human-date :date="scope.obj.creation_date"></human-date>
+            <human-date :date="scope.obj.creation_date" />
           </td>
         </template>
       </action-table>
@@ -85,16 +165,18 @@
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="selectPage"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-        ></pagination>
+        @page-changed="selectPage"
+      />
 
       <span v-if="result && result.results.length > 0">
-        <translate translate-context="Content/*/Paragraph"
-          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
+        <translate
+          translate-context="Content/*/Paragraph"
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
+        >
           Showing results %{ start }-%{ end } on %{ total }
         </translate>
       </span>
@@ -106,25 +188,24 @@
 import axios from 'axios'
 import _ from '@/lodash'
 import time from '@/utils/time'
-import {normalizeQuery, parseTokens} from '@/search'
+import { normalizeQuery, parseTokens } from '@/search'
 import Pagination from '@/components/Pagination'
 import ActionTable from '@/components/common/ActionTable'
 import OrderingMixin from '@/components/mixins/Ordering'
 import TranslationsMixin from '@/components/mixins/Translations'
 import SmartSearchMixin from '@/components/mixins/SmartSearch'
 
-
 export default {
-  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
-  props: {
-    filters: {type: Object, required: false},
-  },
   components: {
     Pagination,
     ActionTable
   },
+  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
+  props: {
+    filters: { type: Object, required: false, default: () => { return {} } }
+  },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
     return {
       time,
       isLoading: false,
@@ -140,36 +221,10 @@ export default {
       orderingOptions: [
         ['creation_date', 'creation_date'],
         ['release_date', 'release_date'],
-        ["name", "name"],
+        ['name', 'name']
       ]
     }
   },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search.query,
-        'ordering': this.getOrderingAsString()
-      }, this.filters)
-      let self = this
-      self.isLoading = true
-      self.checked = []
-      axios.get('/manage/library/albums/', {params: params}).then((response) => {
-        self.result = response.data
-        self.isLoading = false
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    },
-    selectPage: function (page) {
-      this.page = page
-    },
-  },
   computed: {
     labels () {
       return {
@@ -178,7 +233,7 @@ export default {
       }
     },
     actionFilters () {
-      var currentFilters = {
+      const currentFilters = {
         q: this.search.query
       }
       if (this.filters) {
@@ -188,8 +243,8 @@ export default {
       }
     },
     actions () {
-      let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
-      let confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible.')
+      const deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
+      const confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected albums will be removed, as well as associated tracks, uploads, favorites and listening history. This action is irreversible.')
       return [
         {
           name: 'delete',
@@ -197,8 +252,8 @@ export default {
           confirmationMessage: confirmationMessage,
           isDangerous: true,
           allowAll: false,
-          confirmColor: 'danger',
-        },
+          confirmColor: 'danger'
+        }
       ]
     }
   },
@@ -216,6 +271,32 @@ export default {
     orderingDirection () {
       this.fetchData()
     }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search.query,
+        ordering: this.getOrderingAsString()
+      }, this.filters)
+      const self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/library/albums/', { params: params }).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/library/ArtistsTable.vue b/front/src/components/manage/library/ArtistsTable.vue
index 454496d35..fa332278d 100644
--- a/front/src/components/manage/library/ArtistsTable.vue
+++ b/front/src/components/manage/library/ArtistsTable.vue
@@ -5,54 +5,123 @@
         <div class="ui six wide field">
           <label for="artists-serarch"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
           <form @submit.prevent="search.query = $refs.search.value">
-            <input id="artists-search" name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" />
+            <input
+              id="artists-search"
+              ref="search"
+              name="search"
+              type="text"
+              :value="search.query"
+              :placeholder="labels.searchPlaceholder"
+            >
           </form>
         </div>
         <div class="field">
           <label for="artists-category"><translate translate-context="*/*/*">Category</translate></label>
-          <select id="artists-category" class="ui dropdown" @change="addSearchToken('category', $event.target.value)" :value="getTokenValue('category', '')">
-            <option value=""><translate translate-context="Content/*/Dropdown">All</translate></option>
-            <option value="podcast">{{ sharedLabels.fields.content_category.choices.podcast }}</option>
-            <option value="music">{{ sharedLabels.fields.content_category.choices.music }}</option>
-            <option value="other">{{ sharedLabels.fields.content_category.choices.other }}</option>
+          <select
+            id="artists-category"
+            class="ui dropdown"
+            :value="getTokenValue('category', '')"
+            @change="addSearchToken('category', $event.target.value)"
+          >
+            <option value="">
+              <translate translate-context="Content/*/Dropdown">
+                All
+              </translate>
+            </option>
+            <option value="podcast">
+              {{ sharedLabels.fields.content_category.choices.podcast }}
+            </option>
+            <option value="music">
+              {{ sharedLabels.fields.content_category.choices.music }}
+            </option>
+            <option value="other">
+              {{ sharedLabels.fields.content_category.choices.other }}
+            </option>
           </select>
         </div>
         <div class="field">
           <label for="artists-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-          <select id="artists-ordering" class="ui dropdown" v-model="ordering">
-            <option v-for="option in orderingOptions" :value="option[0]">
+          <select
+            id="artists-ordering"
+            v-model="ordering"
+            class="ui dropdown"
+          >
+            <option
+              v-for="(option, key) in orderingOptions"
+              :key="key"
+              :value="option[0]"
+            >
               {{ sharedLabels.filters[option[1]] }}
             </option>
           </select>
         </div>
         <div class="field">
           <label for="artists-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
-          <select id="artists-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-            <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-            <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+          <select
+            id="artists-ordering-direction"
+            v-model="orderingDirection"
+            class="ui dropdown"
+          >
+            <option value="+">
+              <translate translate-context="Content/Search/Dropdown">
+                Ascending
+              </translate>
+            </option>
+            <option value="-">
+              <translate translate-context="Content/Search/Dropdown">
+                Descending
+              </translate>
+            </option>
           </select>
         </div>
       </div>
-      </div>
+    </div>
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-          <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <action-table
         v-if="result"
-        @action-launched="fetchData"
         :objects-data="result"
         :actions="actions"
         action-url="manage/library/artists/action/"
-        :filters="actionFilters">
+        :filters="actionFilters"
+        @action-launched="fetchData"
+      >
         <template slot="header-cells">
-          <th><translate translate-context="*/*/*/Noun">Name</translate></th>
-          <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th>
-          <th><translate translate-context="*/*/*">Albums</translate></th>
-          <th><translate translate-context="*/*/*">Tracks</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Name
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/*/Noun">
+              Domain
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Albums
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Tracks
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Creation date
+            </translate>
+          </th>
         </template>
-        <template slot="row-cells" slot-scope="scope">
+        <template
+          slot="row-cells"
+          slot-scope="scope"
+        >
           <td>
             <router-link :to="getUrl(scope.obj)">
               {{ scope.obj.name }}
@@ -61,12 +130,22 @@
           <td>
             <template v-if="!scope.obj.is_local">
               <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.domain }}">
-                <i class="wrench icon"></i>
+                <i class="wrench icon" />
               </router-link>
-              <a href="" class="discrete link" @click.prevent="addSearchToken('domain', scope.obj.domain)" :title="scope.obj.domain">{{ scope.obj.domain }}</a>
+              <a
+                href=""
+                class="discrete link"
+                :title="scope.obj.domain"
+                @click.prevent="addSearchToken('domain', scope.obj.domain)"
+              >{{ scope.obj.domain }}</a>
             </template>
-            <a href="" v-else class="ui tiny accent icon link label" @click.prevent="addSearchToken('domain', scope.obj.domain)">
-              <i class="home icon"></i>
+            <a
+              v-else
+              href=""
+              class="ui tiny accent icon link label"
+              @click.prevent="addSearchToken('domain', scope.obj.domain)"
+            >
+              <i class="home icon" />
               <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
             </a>
           </td>
@@ -77,7 +156,7 @@
             {{ scope.obj.tracks_count }}
           </td>
           <td>
-            <human-date :date="scope.obj.creation_date"></human-date>
+            <human-date :date="scope.obj.creation_date" />
           </td>
         </template>
       </action-table>
@@ -85,16 +164,18 @@
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="selectPage"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-        ></pagination>
+        @page-changed="selectPage"
+      />
 
       <span v-if="result && result.results.length > 0">
-        <translate translate-context="Content/*/Paragraph"
-          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
+        <translate
+          translate-context="Content/*/Paragraph"
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
+        >
           Showing results %{ start }-%{ end } on %{ total }
         </translate>
       </span>
@@ -106,25 +187,24 @@
 import axios from 'axios'
 import _ from '@/lodash'
 import time from '@/utils/time'
-import {normalizeQuery, parseTokens} from '@/search'
+import { normalizeQuery, parseTokens } from '@/search'
 import Pagination from '@/components/Pagination'
 import ActionTable from '@/components/common/ActionTable'
 import OrderingMixin from '@/components/mixins/Ordering'
 import TranslationsMixin from '@/components/mixins/Translations'
 import SmartSearchMixin from '@/components/mixins/SmartSearch'
 
-
 export default {
-  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
-  props: {
-    filters: {type: Object, required: false},
-  },
   components: {
     Pagination,
     ActionTable
   },
+  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
+  props: {
+    filters: { type: Object, required: false, default: () => { return {} } }
+  },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
     return {
       time,
       isLoading: false,
@@ -139,42 +219,10 @@ export default {
       ordering: defaultOrdering.field,
       orderingOptions: [
         ['creation_date', 'creation_date'],
-        ["name", "name"],
+        ['name', 'name']
       ]
     }
   },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    getUrl (artist) {
-      if (artist.channel) {
-        return {name: 'manage.channels.detail', params: {id: artist.channel }}
-      }
-      return {name: 'manage.library.artists.detail', params: {id: artist.id }}
-    },
-    fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search.query,
-        'ordering': this.getOrderingAsString()
-      }, this.filters)
-      let self = this
-      self.isLoading = true
-      self.checked = []
-      axios.get('/manage/library/artists/', {params: params}).then((response) => {
-        self.result = response.data
-        self.isLoading = false
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    },
-    selectPage: function (page) {
-      this.page = page
-    },
-  },
   computed: {
     labels () {
       return {
@@ -182,7 +230,7 @@ export default {
       }
     },
     actionFilters () {
-      var currentFilters = {
+      const currentFilters = {
         q: this.search.query
       }
       if (this.filters) {
@@ -192,8 +240,8 @@ export default {
       }
     },
     actions () {
-      let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
-      let confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible.')
+      const deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
+      const confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible.')
       return [
         {
           name: 'delete',
@@ -201,8 +249,8 @@ export default {
           confirmationMessage: confirmationMessage,
           isDangerous: true,
           allowAll: false,
-          confirmColor: 'danger',
-        },
+          confirmColor: 'danger'
+        }
       ]
     }
   },
@@ -220,6 +268,38 @@ export default {
     orderingDirection () {
       this.fetchData()
     }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    getUrl (artist) {
+      if (artist.channel) {
+        return { name: 'manage.channels.detail', params: { id: artist.channel } }
+      }
+      return { name: 'manage.library.artists.detail', params: { id: artist.id } }
+    },
+    fetchData () {
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search.query,
+        ordering: this.getOrderingAsString()
+      }, this.filters)
+      const self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/library/artists/', { params: params }).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/library/EditsCardList.vue b/front/src/components/manage/library/EditsCardList.vue
index 01196238e..d253aaf69 100644
--- a/front/src/components/manage/library/EditsCardList.vue
+++ b/front/src/components/manage/library/EditsCardList.vue
@@ -1,77 +1,127 @@
 <template>
   <div class="ui text container">
-    <slot></slot>
+    <slot />
     <div class="ui inline form">
       <div class="fields">
         <div class="ui field">
           <label for="search-edits"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
           <form @submit.prevent="search.query = $refs.search.value">
-            <input id="search-edits" name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" />
+            <input
+              id="search-edits"
+              ref="search"
+              name="search"
+              type="text"
+              :value="search.query"
+              :placeholder="labels.searchPlaceholder"
+            >
           </form>
         </div>
         <div class="field">
           <label for="edit-status"><translate translate-context="*/*/*">Status</translate></label>
-          <select id="edit-status" class="ui dropdown" @change="addSearchToken('is_approved', $event.target.value)" :value="getTokenValue('is_approved', '')">
+          <select
+            id="edit-status"
+            class="ui dropdown"
+            :value="getTokenValue('is_approved', '')"
+            @change="addSearchToken('is_approved', $event.target.value)"
+          >
             <option value="">
-              <translate translate-context="Content/*/Dropdown">All</translate>
+              <translate translate-context="Content/*/Dropdown">
+                All
+              </translate>
             </option>
             <option value="null">
-              <translate translate-context="Content/Admin/*/Noun">Pending review</translate>
+              <translate translate-context="Content/Admin/*/Noun">
+                Pending review
+              </translate>
             </option>
             <option value="yes">
-              <translate translate-context="Content/*/*/Short">Approved</translate>
+              <translate translate-context="Content/*/*/Short">
+                Approved
+              </translate>
             </option>
             <option value="no">
-              <translate translate-context="Content/Library/*/Short">Rejected</translate>
+              <translate translate-context="Content/Library/*/Short">
+                Rejected
+              </translate>
             </option>
           </select>
         </div>
         <div class="field">
           <label for="edit-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-          <select id="edit-ordering" class="ui dropdown" v-model="ordering">
-            <option v-for="option in orderingOptions" :value="option[0]">
+          <select
+            id="edit-ordering"
+            v-model="ordering"
+            class="ui dropdown"
+          >
+            <option
+              v-for="(option, key) in orderingOptions"
+              :key="key"
+              :value="option[0]"
+            >
               {{ sharedLabels.filters[option[1]] }}
             </option>
           </select>
         </div>
         <div class="field">
           <label for="edit-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label>
-          <select id="edit-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-            <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-            <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+          <select
+            id="edit-ordering-direction"
+            v-model="orderingDirection"
+            class="ui dropdown"
+          >
+            <option value="+">
+              <translate translate-context="Content/Search/Dropdown">
+                Ascending
+              </translate>
+            </option>
+            <option value="-">
+              <translate translate-context="Content/Search/Dropdown">
+                Descending
+              </translate>
+            </option>
           </select>
         </div>
       </div>
     </div>
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <div v-else-if="result && result.count > 0">
         <edit-card
+          v-for="obj in result.results"
+          :key="obj.uuid"
           :obj="obj"
           :current-state="getCurrentState(obj.target)"
-          v-for="obj in result.results"
           @deleted="handle('delete', obj.uuid, null)"
           @approved="handle('approved', obj.uuid, $event)"
-          :key="obj.uuid" />
+        />
       </div>
-      <empty-state v-else :refresh="true" @refresh="fetchData()"></empty-state>
+      <empty-state
+        v-else
+        :refresh="true"
+        @refresh="fetchData()"
+      />
     </div>
-    <div class="ui hidden divider"></div>
+    <div class="ui hidden divider" />
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="selectPage"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-        ></pagination>
+        @page-changed="selectPage"
+      />
 
       <span v-if="result && result.results.length > 0">
-        <translate translate-context="Content/*/Paragraph"
-          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
+        <translate
+          translate-context="Content/*/Paragraph"
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
+        >
           Showing results %{ start }-%{ end } on %{ total }
         </translate>
       </span>
@@ -87,23 +137,22 @@ import Pagination from '@/components/Pagination'
 import OrderingMixin from '@/components/mixins/Ordering'
 import TranslationsMixin from '@/components/mixins/Translations'
 import EditCard from '@/components/library/EditCard'
-import {normalizeQuery, parseTokens} from '@/search'
+import { normalizeQuery, parseTokens } from '@/search'
 import SmartSearchMixin from '@/components/mixins/SmartSearch'
 
 import edits from '@/edits'
 
-
 export default {
-  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
-  props: {
-    filters: {type: Object, required: false}
-  },
   components: {
     Pagination,
     EditCard
   },
+  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
+  props: {
+    filters: { type: Object, required: false, default: () => { return {} } }
+  },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
     return {
       time,
       isLoading: false,
@@ -118,28 +167,50 @@ export default {
       ordering: defaultOrdering.field,
       orderingOptions: [
         ['creation_date', 'creation_date'],
-        ['applied_date', 'applied_date'],
+        ['applied_date', 'applied_date']
       ],
       targets: {
         track: {}
       }
     }
   },
+  computed: {
+    labels () {
+      return {
+        searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by account, summary, domain…')
+      }
+    }
+  },
+  watch: {
+    search (newValue) {
+      this.page = 1
+      this.fetchData()
+    },
+    page () {
+      this.fetchData()
+    },
+    ordering () {
+      this.fetchData()
+    },
+    orderingDirection () {
+      this.fetchData()
+    }
+  },
   created () {
     this.fetchData()
   },
   methods: {
     fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search.query,
-        'ordering': this.getOrderingAsString()
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search.query,
+        ordering: this.getOrderingAsString()
       }, this.filters)
-      let self = this
+      const self = this
       self.isLoading = true
       this.result = null
-      axios.get('mutations/', {params: params}).then((response) => {
+      axios.get('mutations/', { params: params }).then((response) => {
         self.result = response.data
         self.isLoading = false
         self.fetchTargets()
@@ -151,25 +222,25 @@ export default {
     fetchTargets () {
       // we request target data via the API so we can display previous state
       // additionnal data next to the edit card
-      let self = this
-      let typesAndIds = {
+      const self = this
+      const typesAndIds = {
         track: {
           url: 'tracks/',
-          ids: [],
+          ids: []
         }
       }
       this.result.results.forEach((m) => {
         if (!m.target || !typesAndIds[m.target.type]) {
           return
         }
-        typesAndIds[m.target.type]['ids'].push(m.target.id)
+        typesAndIds[m.target.type].ids.push(m.target.id)
       })
       Object.keys(typesAndIds).forEach((k) => {
-        let config = typesAndIds[k]
+        const config = typesAndIds[k]
         if (config.ids.length === 0) {
           return
         }
-        axios.get(config.url, {params: {id: _.uniq(config.ids), hidden: 'null'}}).then((response) => {
+        axios.get(config.url, { params: { id: _.uniq(config.ids), hidden: 'null' } }).then((response) => {
           response.data.results.forEach((e) => {
             self.$set(self.targets[k], e.id, {
               payload: e,
@@ -204,28 +275,6 @@ export default {
       }
       return {}
     }
-  },
-  computed: {
-    labels () {
-      return {
-        searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by account, summary, domain…')
-      }
-    },
-  },
-  watch: {
-    search (newValue) {
-      this.page = 1
-      this.fetchData()
-    },
-    page () {
-      this.fetchData()
-    },
-    ordering () {
-      this.fetchData()
-    },
-    orderingDirection () {
-      this.fetchData()
-    }
   }
 }
 </script>
diff --git a/front/src/components/manage/library/LibrariesTable.vue b/front/src/components/manage/library/LibrariesTable.vue
index b5bb8b9b8..3c3cc77c4 100644
--- a/front/src/components/manage/library/LibrariesTable.vue
+++ b/front/src/components/manage/library/LibrariesTable.vue
@@ -5,74 +5,168 @@
         <div class="ui six wide field">
           <label for="libraries-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
           <form @submit.prevent="search.query = $refs.search.value">
-            <input id="libraries-search" name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" />
+            <input
+              id="libraries-search"
+              ref="search"
+              name="search"
+              type="text"
+              :value="search.query"
+              :placeholder="labels.searchPlaceholder"
+            >
           </form>
         </div>
         <div class="field">
           <label for="libraries-visibility"><translate translate-context="*/*/*">Visibility</translate></label>
-          <select id="libraries-visibility" class="ui dropdown" @change="addSearchToken('privacy_level', $event.target.value)" :value="getTokenValue('privacy_level', '')">
-            <option value=""><translate translate-context="Content/*/Dropdown">All</translate></option>
-            <option value="me">{{ sharedLabels.fields.privacy_level.shortChoices.me }}</option>
-            <option value="instance">{{ sharedLabels.fields.privacy_level.shortChoices.instance }}</option>
-            <option value="everyone">{{ sharedLabels.fields.privacy_level.shortChoices.everyone }}</option>
+          <select
+            id="libraries-visibility"
+            class="ui dropdown"
+            :value="getTokenValue('privacy_level', '')"
+            @change="addSearchToken('privacy_level', $event.target.value)"
+          >
+            <option value="">
+              <translate translate-context="Content/*/Dropdown">
+                All
+              </translate>
+            </option>
+            <option value="me">
+              {{ sharedLabels.fields.privacy_level.shortChoices.me }}
+            </option>
+            <option value="instance">
+              {{ sharedLabels.fields.privacy_level.shortChoices.instance }}
+            </option>
+            <option value="everyone">
+              {{ sharedLabels.fields.privacy_level.shortChoices.everyone }}
+            </option>
           </select>
         </div>
         <div class="field">
           <label for="libraries-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-          <select id="libraries-ordering" class="ui dropdown" v-model="ordering">
-            <option v-for="option in orderingOptions" :value="option[0]">
+          <select
+            id="libraries-ordering"
+            v-model="ordering"
+            class="ui dropdown"
+          >
+            <option
+              v-for="(option, key) in orderingOptions"
+              :key="key"
+              :value="option[0]"
+            >
               {{ sharedLabels.filters[option[1]] }}
             </option>
           </select>
         </div>
         <div class="field">
           <label for="libraries-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
-          <select id="libraries-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-            <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-            <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+          <select
+            id="libraries-ordering-direction"
+            v-model="orderingDirection"
+            class="ui dropdown"
+          >
+            <option value="+">
+              <translate translate-context="Content/Search/Dropdown">
+                Ascending
+              </translate>
+            </option>
+            <option value="-">
+              <translate translate-context="Content/Search/Dropdown">
+                Descending
+              </translate>
+            </option>
           </select>
         </div>
       </div>
-      </div>
+    </div>
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-          <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <action-table
         v-if="result"
-        @action-launched="fetchData"
         :objects-data="result"
         :actions="actions"
         action-url="manage/library/libraries/action/"
-        :filters="actionFilters">
+        :filters="actionFilters"
+        @action-launched="fetchData"
+      >
         <template slot="header-cells">
-          <th><translate translate-context="*/*/*/Noun">Name</translate></th>
-          <th><translate translate-context="*/*/*/Noun">Account</translate></th>
-          <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th>
-          <th><translate translate-context="*/*/*">Visibility</translate></th>
-          <th><translate translate-context="*/*/*">Uploads</translate></th>
-          <th><translate translate-context="Content/Federation/*/Noun">Followers</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Name
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Account
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/*/Noun">
+              Domain
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Visibility
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Uploads
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Federation/*/Noun">
+              Followers
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Creation date
+            </translate>
+          </th>
         </template>
-        <template slot="row-cells" slot-scope="scope">
+        <template
+          slot="row-cells"
+          slot-scope="scope"
+        >
           <td>
-            <router-link :to="{name: 'manage.library.libraries.detail', params: {id: scope.obj.uuid }}">{{ scope.obj.name }}</router-link>
+            <router-link :to="{name: 'manage.library.libraries.detail', params: {id: scope.obj.uuid }}">
+              {{ scope.obj.name }}
+            </router-link>
           </td>
           <td>
             <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: scope.obj.actor.full_username }}">
-              <i class="wrench icon"></i>
+              <i class="wrench icon" />
             </router-link>
-            <a href="" class="discrete link" @click.prevent="addSearchToken('account', scope.obj.actor.full_username)" :title="scope.obj.actor.full_username">{{ scope.obj.actor.preferred_username }}</a>
+            <a
+              href=""
+              class="discrete link"
+              :title="scope.obj.actor.full_username"
+              @click.prevent="addSearchToken('account', scope.obj.actor.full_username)"
+            >{{ scope.obj.actor.preferred_username }}</a>
           </td>
           <td>
             <template v-if="!scope.obj.is_local">
               <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.domain }}">
-                <i class="wrench icon"></i>
+                <i class="wrench icon" />
               </router-link>
-              <a href="" class="discrete link" @click.prevent="addSearchToken('domain', scope.obj.domain)" :title="scope.obj.domain">{{ scope.obj.domain }}</a>
+              <a
+                href=""
+                class="discrete link"
+                :title="scope.obj.domain"
+                @click.prevent="addSearchToken('domain', scope.obj.domain)"
+              >{{ scope.obj.domain }}</a>
             </template>
-            <a href="" v-else class="ui tiny accent icon link label" @click.prevent="addSearchToken('domain', scope.obj.domain)">
-              <i class="home icon"></i>
+            <a
+              v-else
+              href=""
+              class="ui tiny accent icon link label"
+              @click.prevent="addSearchToken('domain', scope.obj.domain)"
+            >
+              <i class="home icon" />
               <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
             </a>
           </td>
@@ -80,8 +174,9 @@
             <a
               href=""
               class="discrete link"
+              :title="sharedLabels.fields.privacy_level.shortChoices[scope.obj.privacy_level]"
               @click.prevent="addSearchToken('privacy_level', scope.obj.privacy_level)"
-              :title="sharedLabels.fields.privacy_level.shortChoices[scope.obj.privacy_level]">
+            >
               {{ sharedLabels.fields.privacy_level.shortChoices[scope.obj.privacy_level] }}
             </a>
           </td>
@@ -92,7 +187,7 @@
             {{ scope.obj.followers_count }}
           </td>
           <td>
-            <human-date :date="scope.obj.creation_date"></human-date>
+            <human-date :date="scope.obj.creation_date" />
           </td>
         </template>
       </action-table>
@@ -100,16 +195,18 @@
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="selectPage"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-        ></pagination>
+        @page-changed="selectPage"
+      />
 
       <span v-if="result && result.results.length > 0">
-        <translate translate-context="Content/*/Paragraph"
-          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
+        <translate
+          translate-context="Content/*/Paragraph"
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
+        >
           Showing results %{ start }-%{ end } on %{ total }
         </translate>
       </span>
@@ -121,25 +218,24 @@
 import axios from 'axios'
 import _ from '@/lodash'
 import time from '@/utils/time'
-import {normalizeQuery, parseTokens} from '@/search'
+import { normalizeQuery, parseTokens } from '@/search'
 import Pagination from '@/components/Pagination'
 import ActionTable from '@/components/common/ActionTable'
 import OrderingMixin from '@/components/mixins/Ordering'
 import TranslationsMixin from '@/components/mixins/Translations'
 import SmartSearchMixin from '@/components/mixins/SmartSearch'
 
-
 export default {
-  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
-  props: {
-    filters: {type: Object, required: false},
-  },
   components: {
     Pagination,
     ActionTable
   },
+  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
+  props: {
+    filters: { type: Object, required: false, default: () => { return {} } }
+  },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
     return {
       time,
       isLoading: false,
@@ -155,36 +251,10 @@ export default {
       orderingOptions: [
         ['creation_date', 'creation_date'],
         ['followers_count', 'followers'],
-        ['uploads_count', 'uploads'],
+        ['uploads_count', 'uploads']
       ]
     }
   },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search.query,
-        'ordering': this.getOrderingAsString()
-      }, this.filters)
-      let self = this
-      self.isLoading = true
-      self.checked = []
-      axios.get('/manage/library/libraries/', {params: params}).then((response) => {
-        self.result = response.data
-        self.isLoading = false
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    },
-    selectPage: function (page) {
-      this.page = page
-    },
-  },
   computed: {
     labels () {
       return {
@@ -192,7 +262,7 @@ export default {
       }
     },
     actionFilters () {
-      var currentFilters = {
+      const currentFilters = {
         q: this.search.query
       }
       if (this.filters) {
@@ -202,8 +272,8 @@ export default {
       }
     },
     actions () {
-      let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
-      let confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected library will be removed, as well as associated uploads and follows. This action is irreversible.')
+      const deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
+      const confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected library will be removed, as well as associated uploads and follows. This action is irreversible.')
       return [
         {
           name: 'delete',
@@ -211,8 +281,8 @@ export default {
           confirmationMessage: confirmationMessage,
           isDangerous: true,
           allowAll: false,
-          confirmColor: 'danger',
-        },
+          confirmColor: 'danger'
+        }
       ]
     }
   },
@@ -230,6 +300,32 @@ export default {
     orderingDirection () {
       this.fetchData()
     }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search.query,
+        ordering: this.getOrderingAsString()
+      }, this.filters)
+      const self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/library/libraries/', { params: params }).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/library/TagsTable.vue b/front/src/components/manage/library/TagsTable.vue
index 60fa74eac..a7f1f5759 100644
--- a/front/src/components/manage/library/TagsTable.vue
+++ b/front/src/components/manage/library/TagsTable.vue
@@ -5,47 +5,104 @@
         <div class="ui six wide field">
           <label for="tags-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
           <form @submit.prevent="search.query = $refs.search.value">
-            <input id="tags-search" name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" />
+            <input
+              id="tags-search"
+              ref="search"
+              name="search"
+              type="text"
+              :value="search.query"
+              :placeholder="labels.searchPlaceholder"
+            >
           </form>
         </div>
         <div class="field">
           <label for="tags-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-          <select id="tags-ordering" class="ui dropdown" v-model="ordering">
-            <option v-for="option in orderingOptions" :value="option[0]">
+          <select
+            id="tags-ordering"
+            v-model="ordering"
+            class="ui dropdown"
+          >
+            <option
+              v-for="(option, key) in orderingOptions"
+              :key="key"
+              :value="option[0]"
+            >
               {{ sharedLabels.filters[option[1]] }}
             </option>
           </select>
         </div>
         <div class="field">
           <label for="tags-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
-          <select id="tags-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-            <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-            <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+          <select
+            id="tags-ordering-direction"
+            v-model="orderingDirection"
+            class="ui dropdown"
+          >
+            <option value="+">
+              <translate translate-context="Content/Search/Dropdown">
+                Ascending
+              </translate>
+            </option>
+            <option value="-">
+              <translate translate-context="Content/Search/Dropdown">
+                Descending
+              </translate>
+            </option>
           </select>
         </div>
       </div>
     </div>
-    <import-status-modal :upload="detailedUpload" :show.sync="showUploadDetailModal" />
+    <import-status-modal
+      :upload="detailedUpload"
+      :show.sync="showUploadDetailModal"
+    />
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-          <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <action-table
         v-if="result"
-        @action-launched="fetchData"
         :objects-data="result"
         :actions="actions"
         action-url="manage/tags/action/"
-        idField="name"
-        :filters="actionFilters">
+        id-field="name"
+        :filters="actionFilters"
+        @action-launched="fetchData"
+      >
         <template slot="header-cells">
-          <th><translate translate-context="*/*/*/Noun">Name</translate></th>
-          <th><translate translate-context="*/*/*/Noun">Artists</translate></th>
-          <th><translate translate-context="*/*/*">Albums</translate></th>
-          <th><translate translate-context="*/*/*">Tracks</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Name
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Artists
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Albums
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Tracks
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Creation date
+            </translate>
+          </th>
         </template>
-        <template slot="row-cells" slot-scope="scope">
+        <template
+          slot="row-cells"
+          slot-scope="scope"
+        >
           <td>
             <router-link :to="{name: 'manage.library.tags.detail', params: {id: scope.obj.name }}">
               {{ scope.obj.name|truncate(30, "…", true) }}
@@ -61,7 +118,7 @@
             {{ scope.obj.tracks_count }}
           </td>
           <td>
-            <human-date :date="scope.obj.creation_date"></human-date>
+            <human-date :date="scope.obj.creation_date" />
           </td>
         </template>
       </action-table>
@@ -69,16 +126,18 @@
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="selectPage"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-        ></pagination>
+        @page-changed="selectPage"
+      />
 
       <span v-if="result && result.results.length > 0">
-        <translate translate-context="Content/*/Paragraph"
-          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
+        <translate
+          translate-context="Content/*/Paragraph"
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
+        >
           Showing results %{ start }-%{ end } on %{ total }
         </translate>
       </span>
@@ -90,7 +149,7 @@
 import axios from 'axios'
 import _ from '@/lodash'
 import time from '@/utils/time'
-import {normalizeQuery, parseTokens} from '@/search'
+import { normalizeQuery, parseTokens } from '@/search'
 import Pagination from '@/components/Pagination'
 import ActionTable from '@/components/common/ActionTable'
 import OrderingMixin from '@/components/mixins/Ordering'
@@ -98,19 +157,18 @@ import TranslationsMixin from '@/components/mixins/Translations'
 import SmartSearchMixin from '@/components/mixins/SmartSearch'
 import ImportStatusModal from '@/components/library/ImportStatusModal'
 
-
 export default {
-  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
-  props: {
-    filters: {type: Object, required: false},
-  },
   components: {
     Pagination,
     ActionTable,
     ImportStatusModal
   },
+  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
+  props: {
+    filters: { type: Object, required: false, default: () => { return {} } }
+  },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
     return {
       detailedUpload: null,
       showUploadDetailModal: false,
@@ -129,36 +187,10 @@ export default {
         ['creation_date', 'creation_date'],
         ['name', 'name'],
         ['length', 'length'],
-        ['items_count', 'items_count'],
+        ['items_count', 'items_count']
       ]
     }
   },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search.query,
-        'ordering': this.getOrderingAsString()
-      }, this.filters)
-      let self = this
-      self.isLoading = true
-      self.checked = []
-      axios.get('/manage/tags/', {params: params}).then((response) => {
-        self.isLoading = false
-        self.result = response.data
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    },
-    selectPage: function (page) {
-      this.page = page
-    },
-  },
   computed: {
     labels () {
       return {
@@ -166,7 +198,7 @@ export default {
       }
     },
     actionFilters () {
-      var currentFilters = {
+      const currentFilters = {
         q: this.search.query
       }
       if (this.filters) {
@@ -176,8 +208,8 @@ export default {
       }
     },
     actions () {
-      let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
-      let confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected tag will be removed and unlinked with existing content, if any. This action is irreversible.')
+      const deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
+      const confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected tag will be removed and unlinked with existing content, if any. This action is irreversible.')
       return [
         {
           name: 'delete',
@@ -185,8 +217,8 @@ export default {
           confirmationMessage: confirmationMessage,
           isDangerous: true,
           allowAll: false,
-          confirmColor: 'danger',
-        },
+          confirmColor: 'danger'
+        }
       ]
     }
   },
@@ -204,6 +236,32 @@ export default {
     orderingDirection () {
       this.fetchData()
     }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search.query,
+        ordering: this.getOrderingAsString()
+      }, this.filters)
+      const self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/tags/', { params: params }).then((response) => {
+        self.isLoading = false
+        self.result = response.data
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/library/TracksTable.vue b/front/src/components/manage/library/TracksTable.vue
index 9c12ea11d..1d565ca19 100644
--- a/front/src/components/manage/library/TracksTable.vue
+++ b/front/src/components/manage/library/TracksTable.vue
@@ -5,81 +5,172 @@
         <div class="ui six wide field">
           <label for="tracks-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
           <form @submit.prevent="search.query = $refs.search.value">
-            <input id="tracks-search" name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" />
+            <input
+              id="tracks-search"
+              ref="search"
+              name="search"
+              type="text"
+              :value="search.query"
+              :placeholder="labels.searchPlaceholder"
+            >
           </form>
         </div>
         <div class="field">
           <label for="tracks-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-          <select id="tracks-ordering" class="ui dropdown" v-model="ordering">
-            <option v-for="option in orderingOptions" :value="option[0]">
+          <select
+            id="tracks-ordering"
+            v-model="ordering"
+            class="ui dropdown"
+          >
+            <option
+              v-for="(option, key) in orderingOptions"
+              :key="key"
+              :value="option[0]"
+            >
               {{ sharedLabels.filters[option[1]] }}
             </option>
           </select>
         </div>
         <div class="field">
           <label for="tracks-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
-          <select id="tracks-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-            <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-            <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+          <select
+            id="tracks-ordering-direction"
+            v-model="orderingDirection"
+            class="ui dropdown"
+          >
+            <option value="+">
+              <translate translate-context="Content/Search/Dropdown">
+                Ascending
+              </translate>
+            </option>
+            <option value="-">
+              <translate translate-context="Content/Search/Dropdown">
+                Descending
+              </translate>
+            </option>
           </select>
         </div>
       </div>
-      </div>
+    </div>
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-          <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <action-table
         v-if="result"
-        @action-launched="fetchData"
         :objects-data="result"
         :actions="actions"
         action-url="manage/library/tracks/action/"
-        :filters="actionFilters">
+        :filters="actionFilters"
+        @action-launched="fetchData"
+      >
         <template slot="header-cells">
-          <th><translate translate-context="*/*/*/Noun">Title</translate></th>
-          <th><translate translate-context="*/*/*">Album</translate></th>
-          <th><translate translate-context="*/*/*/Noun">Artist</translate></th>
-          <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">License</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Title
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Album
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Artist
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/*/Noun">
+              Domain
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              License
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Creation date
+            </translate>
+          </th>
         </template>
-        <template slot="row-cells" slot-scope="scope">
+        <template
+          slot="row-cells"
+          slot-scope="scope"
+        >
           <td>
-            <router-link :to="{name: 'manage.library.tracks.detail', params: {id: scope.obj.id }}">{{ scope.obj.title }}</router-link>
+            <router-link :to="{name: 'manage.library.tracks.detail', params: {id: scope.obj.id }}">
+              {{ scope.obj.title }}
+            </router-link>
           </td>
           <td>
             <template v-if="scope.obj.album">
               <router-link :to="{name: 'manage.library.albums.detail', params: {id: scope.obj.album.id }}">
-                <i class="wrench icon"></i>
+                <i class="wrench icon" />
               </router-link>
-              <a href="" class="discrete link" @click.prevent="addSearchToken('album_id', scope.obj.album.id)" :title="scope.obj.album.title">{{ scope.obj.album.title }}</a>
+              <a
+                href=""
+                class="discrete link"
+                :title="scope.obj.album.title"
+                @click.prevent="addSearchToken('album_id', scope.obj.album.id)"
+              >{{ scope.obj.album.title }}</a>
             </template>
           </td>
           <td>
             <router-link :to="{name: 'manage.library.artists.detail', params: {id: scope.obj.artist.id }}">
-              <i class="wrench icon"></i>
+              <i class="wrench icon" />
             </router-link>
-            <a href="" class="discrete link" @click.prevent="addSearchToken('artist_id', scope.obj.artist.id)" :title="scope.obj.artist.name">{{ scope.obj.artist.name }}</a>
+            <a
+              href=""
+              class="discrete link"
+              :title="scope.obj.artist.name"
+              @click.prevent="addSearchToken('artist_id', scope.obj.artist.id)"
+            >{{ scope.obj.artist.name }}</a>
           </td>
           <td>
             <template v-if="!scope.obj.is_local">
               <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.domain }}">
-                <i class="wrench icon"></i>
+                <i class="wrench icon" />
               </router-link>
-              <a href="" class="discrete link" @click.prevent="addSearchToken('domain', scope.obj.domain)" :title="scope.obj.domain">{{ scope.obj.domain }}</a>
+              <a
+                href=""
+                class="discrete link"
+                :title="scope.obj.domain"
+                @click.prevent="addSearchToken('domain', scope.obj.domain)"
+              >{{ scope.obj.domain }}</a>
             </template>
-            <a href="" v-else class="ui tiny accent icon link label" @click.prevent="addSearchToken('domain', scope.obj.domain)">
-              <i class="home icon"></i>
+            <a
+              v-else
+              href=""
+              class="ui tiny accent icon link label"
+              @click.prevent="addSearchToken('domain', scope.obj.domain)"
+            >
+              <i class="home icon" />
               <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
             </a>
           </td>
           <td>
-            <a href="" v-if="scope.obj.license" class="discrete link" @click.prevent="addSearchToken('license', scope.obj.license)" :title="scope.obj.license">{{ scope.obj.license }}</a>
-            <translate v-else translate-context="*/*/*">N/A</translate>
+            <a
+              v-if="scope.obj.license"
+              href=""
+              class="discrete link"
+              :title="scope.obj.license"
+              @click.prevent="addSearchToken('license', scope.obj.license)"
+            >{{ scope.obj.license }}</a>
+            <translate
+              v-else
+              translate-context="*/*/*"
+            >
+              N/A
+            </translate>
           </td>
           <td>
-            <human-date :date="scope.obj.creation_date"></human-date>
+            <human-date :date="scope.obj.creation_date" />
           </td>
         </template>
       </action-table>
@@ -87,16 +178,18 @@
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="selectPage"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-        ></pagination>
+        @page-changed="selectPage"
+      />
 
       <span v-if="result && result.results.length > 0">
-        <translate translate-context="Content/*/Paragraph"
-          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
+        <translate
+          translate-context="Content/*/Paragraph"
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
+        >
           Showing results %{ start }-%{ end } on %{ total }
         </translate>
       </span>
@@ -108,25 +201,24 @@
 import axios from 'axios'
 import _ from '@/lodash'
 import time from '@/utils/time'
-import {normalizeQuery, parseTokens} from '@/search'
+import { normalizeQuery, parseTokens } from '@/search'
 import Pagination from '@/components/Pagination'
 import ActionTable from '@/components/common/ActionTable'
 import OrderingMixin from '@/components/mixins/Ordering'
 import TranslationsMixin from '@/components/mixins/Translations'
 import SmartSearchMixin from '@/components/mixins/SmartSearch'
 
-
 export default {
-  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
-  props: {
-    filters: {type: Object, required: false},
-  },
   components: {
     Pagination,
     ActionTable
   },
+  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
+  props: {
+    filters: { type: Object, required: false, default: () => { return {} } }
+  },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
     return {
       time,
       isLoading: false,
@@ -140,36 +232,10 @@ export default {
       orderingDirection: defaultOrdering.direction || '+',
       ordering: defaultOrdering.field,
       orderingOptions: [
-        ['creation_date', 'creation_date'],
+        ['creation_date', 'creation_date']
       ]
     }
   },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search.query,
-        'ordering': this.getOrderingAsString()
-      }, this.filters)
-      let self = this
-      self.isLoading = true
-      self.checked = []
-      axios.get('/manage/library/tracks/', {params: params}).then((response) => {
-        self.result = response.data
-        self.isLoading = false
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    },
-    selectPage: function (page) {
-      this.page = page
-    },
-  },
   computed: {
     labels () {
       return {
@@ -177,7 +243,7 @@ export default {
       }
     },
     actionFilters () {
-      var currentFilters = {
+      const currentFilters = {
         q: this.search.query
       }
       if (this.filters) {
@@ -187,8 +253,8 @@ export default {
       }
     },
     actions () {
-      let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
-      let confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible.')
+      const deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
+      const confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected tracks will be removed, as well as associated uploads, favorites and listening history. This action is irreversible.')
       return [
         {
           name: 'delete',
@@ -196,8 +262,8 @@ export default {
           confirmationMessage: confirmationMessage,
           isDangerous: true,
           allowAll: false,
-          confirmColor: 'danger',
-        },
+          confirmColor: 'danger'
+        }
       ]
     }
   },
@@ -215,6 +281,32 @@ export default {
     orderingDirection () {
       this.fetchData()
     }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search.query,
+        ordering: this.getOrderingAsString()
+      }, this.filters)
+      const self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/library/tracks/', { params: params }).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/library/UploadsTable.vue b/front/src/components/manage/library/UploadsTable.vue
index f3f50584a..31f964833 100644
--- a/front/src/components/manage/library/UploadsTable.vue
+++ b/front/src/components/manage/library/UploadsTable.vue
@@ -5,69 +5,182 @@
         <div class="ui six wide field">
           <label for="uploads-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
           <form @submit.prevent="search.query = $refs.search.value">
-            <input id="uploads-search" name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" />
+            <input
+              id="uploads-search"
+              ref="search"
+              name="search"
+              type="text"
+              :value="search.query"
+              :placeholder="labels.searchPlaceholder"
+            >
           </form>
         </div>
         <div class="field">
           <label for="uploads-visibility"><translate translate-context="*/*/*">Visibility</translate></label>
-          <select id="uploads-visibility" class="ui dropdown" @change="addSearchToken('privacy_level', $event.target.value)" :value="getTokenValue('privacy_level', '')">
-            <option value=""><translate translate-context="Content/*/Dropdown">All</translate></option>
-            <option value="me">{{ sharedLabels.fields.privacy_level.shortChoices.me }}</option>
-            <option value="instance">{{ sharedLabels.fields.privacy_level.shortChoices.instance }}</option>
-            <option value="everyone">{{ sharedLabels.fields.privacy_level.shortChoices.everyone }}</option>
+          <select
+            id="uploads-visibility"
+            class="ui dropdown"
+            :value="getTokenValue('privacy_level', '')"
+            @change="addSearchToken('privacy_level', $event.target.value)"
+          >
+            <option value="">
+              <translate translate-context="Content/*/Dropdown">
+                All
+              </translate>
+            </option>
+            <option value="me">
+              {{ sharedLabels.fields.privacy_level.shortChoices.me }}
+            </option>
+            <option value="instance">
+              {{ sharedLabels.fields.privacy_level.shortChoices.instance }}
+            </option>
+            <option value="everyone">
+              {{ sharedLabels.fields.privacy_level.shortChoices.everyone }}
+            </option>
           </select>
         </div>
         <div class="field">
           <label for="uploads-status"><translate translate-context="Content/*/*/Noun">Import status</translate></label>
-          <select id="uploads-status" class="ui dropdown" @change="addSearchToken('status', $event.target.value)" :value="getTokenValue('status', '')">
-            <option value=""><translate translate-context="Content/*/Dropdown">All</translate></option>
-            <option value="pending"><translate translate-context="Content/Library/*/Short">Pending</translate></option>
-            <option value="skipped"><translate translate-context="Content/Library/*">Skipped</translate></option>
-            <option value="errored"><translate translate-context="Content/Library/Dropdown">Failed</translate></option>
-            <option value="finished"><translate translate-context="Content/Library/*">Finished</translate></option>
+          <select
+            id="uploads-status"
+            class="ui dropdown"
+            :value="getTokenValue('status', '')"
+            @change="addSearchToken('status', $event.target.value)"
+          >
+            <option value="">
+              <translate translate-context="Content/*/Dropdown">
+                All
+              </translate>
+            </option>
+            <option value="pending">
+              <translate translate-context="Content/Library/*/Short">
+                Pending
+              </translate>
+            </option>
+            <option value="skipped">
+              <translate translate-context="Content/Library/*">
+                Skipped
+              </translate>
+            </option>
+            <option value="errored">
+              <translate translate-context="Content/Library/Dropdown">
+                Failed
+              </translate>
+            </option>
+            <option value="finished">
+              <translate translate-context="Content/Library/*">
+                Finished
+              </translate>
+            </option>
           </select>
         </div>
         <div class="field">
           <label for="uploads-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-          <select id="uploads-ordering" class="ui dropdown" v-model="ordering">
-            <option v-for="option in orderingOptions" :value="option[0]">
+          <select
+            id="uploads-ordering"
+            v-model="ordering"
+            class="ui dropdown"
+          >
+            <option
+              v-for="(option, key) in orderingOptions"
+              :key="key"
+              :value="option[0]"
+            >
               {{ sharedLabels.filters[option[1]] }}
             </option>
           </select>
         </div>
         <div class="field">
           <label for="uploads-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
-          <select id="uploads-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-            <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-            <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+          <select
+            id="uploads-ordering-direction"
+            v-model="orderingDirection"
+            class="ui dropdown"
+          >
+            <option value="+">
+              <translate translate-context="Content/Search/Dropdown">
+                Ascending
+              </translate>
+            </option>
+            <option value="-">
+              <translate translate-context="Content/Search/Dropdown">
+                Descending
+              </translate>
+            </option>
           </select>
         </div>
       </div>
     </div>
-    <import-status-modal :upload="detailedUpload" :show.sync="showUploadDetailModal" />
+    <import-status-modal
+      :upload="detailedUpload"
+      :show.sync="showUploadDetailModal"
+    />
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-          <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <action-table
         v-if="result"
-        @action-launched="fetchData"
         :objects-data="result"
         :actions="actions"
         action-url="manage/library/uploads/action/"
-        :filters="actionFilters">
+        :filters="actionFilters"
+        @action-launched="fetchData"
+      >
         <template slot="header-cells">
-          <th><translate translate-context="*/*/*/Noun">Name</translate></th>
-          <th><translate translate-context="*/*/*/Noun">Library</translate></th>
-          <th><translate translate-context="*/*/*/Noun">Account</translate></th>
-          <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th>
-          <th><translate translate-context="*/*/*">Visibility</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Import status</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Size</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Accessed date</translate></th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Name
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Library
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Account
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/*/Noun">
+              Domain
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Visibility
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Import status
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Size
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Creation date
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Accessed date
+            </translate>
+          </th>
         </template>
-        <template slot="row-cells" slot-scope="scope">
+        <template
+          slot="row-cells"
+          slot-scope="scope"
+        >
           <td>
             <router-link :to="{name: 'manage.library.uploads.detail', params: {id: scope.obj.uuid }}">
               {{ displayName(scope.obj)|truncate(30, "…", true) }}
@@ -75,28 +188,45 @@
           </td>
           <td>
             <router-link :to="{name: 'manage.library.libraries.detail', params: {id: scope.obj.library.uuid }}">
-              <i class="wrench icon"></i>
+              <i class="wrench icon" />
             </router-link>
-            <a href="" class="discrete link"
+            <a
+              href=""
+              class="discrete link"
+              :title="scope.obj.library.name"
               @click.prevent="addSearchToken('library_id', scope.obj.library.id)"
-              :title="scope.obj.library.name">
+            >
               {{ scope.obj.library.name | truncate(20) }}
             </a>
           </td>
           <td>
-            <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: scope.obj.library.actor.full_username }}">
-            </router-link>
-            <a href="" class="discrete link" @click.prevent="addSearchToken('account', scope.obj.library.actor.full_username)" :title="scope.obj.library.actor.full_username">{{ scope.obj.library.actor.preferred_username }}</a>
+            <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: scope.obj.library.actor.full_username }}" />
+            <a
+              href=""
+              class="discrete link"
+              :title="scope.obj.library.actor.full_username"
+              @click.prevent="addSearchToken('account', scope.obj.library.actor.full_username)"
+            >{{ scope.obj.library.actor.preferred_username }}</a>
           </td>
           <td>
             <template v-if="!scope.obj.is_local">
               <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.domain }}">
-                <i class="wrench icon"></i>
+                <i class="wrench icon" />
               </router-link>
-              <a href="" class="discrete link" @click.prevent="addSearchToken('domain', scope.obj.domain)" :title="scope.obj.domain">{{ scope.obj.domain }}</a>
+              <a
+                href=""
+                class="discrete link"
+                :title="scope.obj.domain"
+                @click.prevent="addSearchToken('domain', scope.obj.domain)"
+              >{{ scope.obj.domain }}</a>
             </template>
-            <a href="" v-else class="ui tiny accent icon link label" @click.prevent="addSearchToken('domain', scope.obj.domain)">
-              <i class="home icon"></i>
+            <a
+              v-else
+              href=""
+              class="ui tiny accent icon link label"
+              @click.prevent="addSearchToken('domain', scope.obj.domain)"
+            >
+              <i class="home icon" />
               <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
             </a>
           </td>
@@ -104,29 +234,52 @@
             <a
               href=""
               class="discrete link"
+              :title="sharedLabels.fields.privacy_level.shortChoices[scope.obj.library.privacy_level]"
               @click.prevent="addSearchToken('privacy_level', scope.obj.library.privacy_level)"
-              :title="sharedLabels.fields.privacy_level.shortChoices[scope.obj.library.privacy_level]">
+            >
               {{ sharedLabels.fields.privacy_level.shortChoices[scope.obj.library.privacy_level] }}
             </a>
           </td>
           <td>
-            <a href="" class="discrete link" @click.prevent="addSearchToken('status', scope.obj.import_status)" :title="sharedLabels.fields.import_status.choices[scope.obj.import_status].help">
+            <a
+              href=""
+              class="discrete link"
+              :title="sharedLabels.fields.import_status.choices[scope.obj.import_status].help"
+              @click.prevent="addSearchToken('status', scope.obj.import_status)"
+            >
               {{ sharedLabels.fields.import_status.choices[scope.obj.import_status].label }}
             </a>
-            <button class="ui tiny basic icon button" :title="sharedLabels.fields.import_status.detailTitle" @click="detailedUpload = scope.obj; showUploadDetailModal = true">
-              <i class="question circle outline icon"></i>
+            <button
+              class="ui tiny basic icon button"
+              :title="sharedLabels.fields.import_status.detailTitle"
+              @click="detailedUpload = scope.obj; showUploadDetailModal = true"
+            >
+              <i class="question circle outline icon" />
             </button>
           </td>
           <td>
             <span v-if="scope.obj.size">{{ scope.obj.size | humanSize }}</span>
-            <translate v-else translate-context="*/*/*">N/A</translate>
+            <translate
+              v-else
+              translate-context="*/*/*"
+            >
+              N/A
+            </translate>
           </td>
           <td>
-            <human-date :date="scope.obj.creation_date"></human-date>
+            <human-date :date="scope.obj.creation_date" />
           </td>
           <td>
-            <human-date v-if="scope.obj.accessed_date" :date="scope.obj.accessed_date"></human-date>
-            <translate v-else translate-context="*/*/*">N/A</translate>
+            <human-date
+              v-if="scope.obj.accessed_date"
+              :date="scope.obj.accessed_date"
+            />
+            <translate
+              v-else
+              translate-context="*/*/*"
+            >
+              N/A
+            </translate>
           </td>
         </template>
       </action-table>
@@ -134,16 +287,18 @@
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="selectPage"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-        ></pagination>
+        @page-changed="selectPage"
+      />
 
       <span v-if="result && result.results.length > 0">
-        <translate translate-context="Content/*/Paragraph"
-          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
+        <translate
+          translate-context="Content/*/Paragraph"
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
+        >
           Showing results %{ start }-%{ end } on %{ total }
         </translate>
       </span>
@@ -155,7 +310,7 @@
 import axios from 'axios'
 import _ from '@/lodash'
 import time from '@/utils/time'
-import {normalizeQuery, parseTokens} from '@/search'
+import { normalizeQuery, parseTokens } from '@/search'
 import Pagination from '@/components/Pagination'
 import ActionTable from '@/components/common/ActionTable'
 import OrderingMixin from '@/components/mixins/Ordering'
@@ -163,19 +318,18 @@ import TranslationsMixin from '@/components/mixins/Translations'
 import SmartSearchMixin from '@/components/mixins/SmartSearch'
 import ImportStatusModal from '@/components/library/ImportStatusModal'
 
-
 export default {
-  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
-  props: {
-    filters: {type: Object, required: false},
-  },
   components: {
     Pagination,
     ActionTable,
     ImportStatusModal
   },
+  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
+  props: {
+    filters: { type: Object, required: false, default: function () { return {} } }
+  },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
     return {
       detailedUpload: null,
       showUploadDetailModal: false,
@@ -196,45 +350,10 @@ export default {
         ['accessed_date', 'accessed_date'],
         ['size', 'size'],
         ['bitrate', 'bitrate'],
-        ['duration', 'duration'],
+        ['duration', 'duration']
       ]
     }
   },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search.query,
-        'ordering': this.getOrderingAsString()
-      }, this.filters)
-      let self = this
-      self.isLoading = true
-      self.checked = []
-      axios.get('/manage/library/uploads/', {params: params}).then((response) => {
-        self.result = response.data
-        self.isLoading = false
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    },
-    selectPage: function (page) {
-      this.page = page
-    },
-    displayName (upload) {
-      if (upload.filename) {
-        return upload.filename
-      }
-      if (upload.source) {
-        return upload.source
-      }
-      return upload.uuid
-    }
-  },
   computed: {
     labels () {
       return {
@@ -242,7 +361,7 @@ export default {
       }
     },
     actionFilters () {
-      var currentFilters = {
+      const currentFilters = {
         q: this.search.query
       }
       if (this.filters) {
@@ -252,8 +371,8 @@ export default {
       }
     },
     actions () {
-      let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
-      let confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected upload will be removed. This action is irreversible.')
+      const deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
+      const confirmationMessage = this.$pgettext('Popup/*/Paragraph', 'The selected upload will be removed. This action is irreversible.')
       return [
         {
           name: 'delete',
@@ -261,8 +380,8 @@ export default {
           confirmationMessage: confirmationMessage,
           isDangerous: true,
           allowAll: false,
-          confirmColor: 'danger',
-        },
+          confirmColor: 'danger'
+        }
       ]
     }
   },
@@ -280,6 +399,41 @@ export default {
     orderingDirection () {
       this.fetchData()
     }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search.query,
+        ordering: this.getOrderingAsString()
+      }, this.filters)
+      const self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/library/uploads/', { params: params }).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    },
+    displayName (upload) {
+      if (upload.filename) {
+        return upload.filename
+      }
+      if (upload.source) {
+        return upload.source
+      }
+      return upload.uuid
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/moderation/AccountsTable.vue b/front/src/components/manage/moderation/AccountsTable.vue
index 124efcbca..2170f635c 100644
--- a/front/src/components/manage/moderation/AccountsTable.vue
+++ b/front/src/components/manage/moderation/AccountsTable.vue
@@ -5,58 +5,128 @@
         <div class="ui six wide field">
           <label for="accounts-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
           <form @submit.prevent="search.query = $refs.search.value">
-            <input id="accounts-search" name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" />
+            <input
+              id="accounts-search"
+              ref="search"
+              name="search"
+              type="text"
+              :value="search.query"
+              :placeholder="labels.searchPlaceholder"
+            >
           </form>
         </div>
         <div class="field">
           <label for="accounts-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-          <select id="accounts-ordering" class="ui dropdown" v-model="ordering">
-            <option v-for="option in orderingOptions" :value="option[0]">
+          <select
+            id="accounts-ordering"
+            v-model="ordering"
+            class="ui dropdown"
+          >
+            <option
+              v-for="(option, key) in orderingOptions"
+              :key="key"
+              :value="option[0]"
+            >
               {{ sharedLabels.filters[option[1]] }}
             </option>
           </select>
         </div>
         <div class="field">
           <label for="accounts-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
-          <select id="accounts-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-            <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-            <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+          <select
+            id="accounts-ordering-direction"
+            v-model="orderingDirection"
+            class="ui dropdown"
+          >
+            <option value="+">
+              <translate translate-context="Content/Search/Dropdown">
+                Ascending
+              </translate>
+            </option>
+            <option value="-">
+              <translate translate-context="Content/Search/Dropdown">
+                Descending
+              </translate>
+            </option>
           </select>
         </div>
       </div>
-      </div>
+    </div>
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-          <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <action-table
         v-if="result"
-        @action-launched="fetchData"
         :objects-data="result"
         :actions="actions"
         action-url="manage/accounts/action/"
-        :filters="actionFilters">
+        :filters="actionFilters"
+        @action-launched="fetchData"
+      >
         <template slot="header-cells">
-          <th><translate translate-context="*/*/*/Noun">Name</translate></th>
-          <th><translate translate-context="Content/Moderation/*/Noun">Domain</translate></th>
-          <th><translate translate-context="*/*/*">Uploads</translate></th>
-          <th><translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate></th>
-          <th><translate translate-context="Content/Moderation/Table.Label/Noun">Last seen</translate></th>
-          <th><translate translate-context="Content/Moderation/Table.Label/Short">Under moderation rule</translate></th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Name
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/*/Noun">
+              Domain
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Uploads
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">
+              First seen
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/Table.Label/Noun">
+              Last seen
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/Table.Label/Short">
+              Under moderation rule
+            </translate>
+          </th>
         </template>
-        <template slot="row-cells" slot-scope="scope">
+        <template
+          slot="row-cells"
+          slot-scope="scope"
+        >
           <td>
-            <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: scope.obj.full_username }}">{{ scope.obj.preferred_username }}</router-link>
+            <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: scope.obj.full_username }}">
+              {{ scope.obj.preferred_username }}
+            </router-link>
           </td>
           <td>
             <template v-if="!scope.obj.user">
               <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.domain }}">
-                <i class="wrench icon"></i>
+                <i class="wrench icon" />
               </router-link>
-              <a href="" class="discrete link" @click.prevent="addSearchToken('domain', scope.obj.domain)" :title="scope.obj.domain">{{ scope.obj.domain }}</a>
+              <a
+                href=""
+                class="discrete link"
+                :title="scope.obj.domain"
+                @click.prevent="addSearchToken('domain', scope.obj.domain)"
+              >{{ scope.obj.domain }}</a>
             </template>
-            <a href="" v-else class="ui tiny accent icon link label" @click.prevent="addSearchToken('domain', scope.obj.domain)">
-              <i class="home icon"></i>
+            <a
+              v-else
+              href=""
+              class="ui tiny accent icon link label"
+              @click.prevent="addSearchToken('domain', scope.obj.domain)"
+            >
+              <i class="home icon" />
               <translate translate-context="Content/Moderation/*/Short, Noun">Local account</translate>
             </a>
           </td>
@@ -64,13 +134,16 @@
             {{ scope.obj.uploads_count }}
           </td>
           <td>
-            <human-date :date="scope.obj.creation_date"></human-date>
+            <human-date :date="scope.obj.creation_date" />
           </td>
           <td>
-            <human-date v-if="scope.obj.last_fetch_date" :date="scope.obj.last_fetch_date"></human-date>
+            <human-date
+              v-if="scope.obj.last_fetch_date"
+              :date="scope.obj.last_fetch_date"
+            />
           </td>
           <td>
-            <span v-if="scope.obj.instance_policy"><i class="shield icon"></i> <translate translate-context="*/*/*">Yes</translate></span>
+            <span v-if="scope.obj.instance_policy"><i class="shield icon" /> <translate translate-context="*/*/*">Yes</translate></span>
           </td>
         </template>
       </action-table>
@@ -78,16 +151,18 @@
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="selectPage"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-        ></pagination>
+        @page-changed="selectPage"
+      />
 
       <span v-if="result && result.results.length > 0">
-        <translate translate-context="Content/*/Paragraph"
-          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
+        <translate
+          translate-context="Content/*/Paragraph"
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
+        >
           Showing results %{ start }-%{ end } on %{ total }
         </translate>
       </span>
@@ -99,25 +174,24 @@
 import axios from 'axios'
 import _ from '@/lodash'
 import time from '@/utils/time'
-import {normalizeQuery, parseTokens} from '@/search'
+import { normalizeQuery, parseTokens } from '@/search'
 import Pagination from '@/components/Pagination'
 import ActionTable from '@/components/common/ActionTable'
 import OrderingMixin from '@/components/mixins/Ordering'
 import TranslationsMixin from '@/components/mixins/Translations'
 import SmartSearchMixin from '@/components/mixins/SmartSearch'
 
-
 export default {
-  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
-  props: {
-    filters: {type: Object, required: false},
-  },
   components: {
     Pagination,
     ActionTable
   },
+  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
+  props: {
+    filters: { type: Object, required: false, default: function () { return {} } }
+  },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
     return {
       time,
       isLoading: false,
@@ -132,39 +206,13 @@ export default {
       ordering: defaultOrdering.field,
       orderingOptions: [
         ['creation_date', 'first_seen'],
-        ["last_fetch_date", "last_seen"],
-        ["preferred_username", "username"],
-        ["domain", "domain"],
-        ["uploads_count", "uploads"],
+        ['last_fetch_date', 'last_seen'],
+        ['preferred_username', 'username'],
+        ['domain', 'domain'],
+        ['uploads_count', 'uploads']
       ]
     }
   },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search.query,
-        'ordering': this.getOrderingAsString()
-      }, this.filters)
-      let self = this
-      self.isLoading = true
-      self.checked = []
-      axios.get('/manage/accounts/', {params: params}).then((response) => {
-        self.result = response.data
-        self.isLoading = false
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    },
-    selectPage: function (page) {
-      this.page = page
-    }
-  },
   computed: {
     labels () {
       return {
@@ -172,7 +220,7 @@ export default {
       }
     },
     actionFilters () {
-      var currentFilters = {
+      const currentFilters = {
         q: this.search.query
       }
       if (this.filters) {
@@ -205,6 +253,32 @@ export default {
     orderingDirection () {
       this.fetchData()
     }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search.query,
+        ordering: this.getOrderingAsString()
+      }, this.filters)
+      const self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/accounts/', { params: params }).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/moderation/DomainsTable.vue b/front/src/components/manage/moderation/DomainsTable.vue
index 56dd457c0..be3953a6d 100644
--- a/front/src/components/manage/moderation/DomainsTable.vue
+++ b/front/src/components/manage/moderation/DomainsTable.vue
@@ -4,57 +4,133 @@
       <div class="fields">
         <div class="ui field">
           <label for="domains-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
-          <input id="domains-search" name="search" type="text" v-model="search" :placeholder="labels.searchPlaceholder" />
+          <input
+            id="domains-search"
+            v-model="search"
+            name="search"
+            type="text"
+            :placeholder="labels.searchPlaceholder"
+          >
         </div>
-        <div class="field" v-if="allowListEnabled">
+        <div
+          v-if="allowListEnabled"
+          class="field"
+        >
           <label for="domains-allow-list"><translate translate-context="Content/Moderation/*/Adjective">Is present on allow-list</translate></label>
-          <select id="domains-allow-list" class="ui dropdown" v-model="allowed">
-            <option :value="null"><translate translate-context="Content/*/Dropdown">All</translate></option>
-            <option :value="true"><translate translate-context="*/*/*">Yes</translate></option>
-            <option :value="false"><translate translate-context="*/*/*">No</translate></option>
+          <select
+            id="domains-allow-list"
+            v-model="allowed"
+            class="ui dropdown"
+          >
+            <option :value="null">
+              <translate translate-context="Content/*/Dropdown">
+                All
+              </translate>
+            </option>
+            <option :value="true">
+              <translate translate-context="*/*/*">
+                Yes
+              </translate>
+            </option>
+            <option :value="false">
+              <translate translate-context="*/*/*">
+                No
+              </translate>
+            </option>
           </select>
         </div>
         <div class="field">
           <label for="domains-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-          <select id="domains-ordering" class="ui dropdown" v-model="ordering">
-            <option v-for="option in orderingOptions" :value="option[0]">
+          <select
+            id="domains-ordering"
+            v-model="ordering"
+            class="ui dropdown"
+          >
+            <option
+              v-for="(option, key) in orderingOptions"
+              :key="key"
+              :value="option[0]"
+            >
               {{ sharedLabels.filters[option[1]] }}
             </option>
           </select>
         </div>
         <div class="field">
           <label for="domains-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate></label>
-          <select id="domains-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-            <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-            <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+          <select
+            id="domains-ordering-direction"
+            v-model="orderingDirection"
+            class="ui dropdown"
+          >
+            <option value="+">
+              <translate translate-context="Content/Search/Dropdown">
+                Ascending
+              </translate>
+            </option>
+            <option value="-">
+              <translate translate-context="Content/Search/Dropdown">
+                Descending
+              </translate>
+            </option>
           </select>
         </div>
       </div>
-      </div>
+    </div>
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <action-table
         v-if="result && result.results.length > 0"
-        @action-launched="fetchData"
         :objects-data="result"
         :actions="actions"
         action-url="manage/federation/domains/action/"
-        idField="name"
-        :filters="actionFilters">
+        id-field="name"
+        :filters="actionFilters"
+        @action-launched="fetchData"
+      >
         <template slot="header-cells">
-          <th><translate translate-context="*/*/*/Noun">Name</translate></th>
-          <th><translate translate-context="*/*/*/Noun">Users</translate></th>
-          <th><translate translate-context="Content/Moderation/*/Noun">Received messages</translate></th>
-          <th><translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate></th>
-          <th><translate translate-context="Content/Moderation/Table.Label/Short">Under moderation rule</translate></th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Name
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*/Noun">
+              Users
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/*/Noun">
+              Received messages
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">
+              First seen
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Moderation/Table.Label/Short">
+              Under moderation rule
+            </translate>
+          </th>
         </template>
-        <template slot="row-cells" slot-scope="scope">
+        <template
+          slot="row-cells"
+          slot-scope="scope"
+        >
           <td>
             <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: scope.obj.name }}">
               {{ scope.obj.name }}
-              <i v-if="allowListEnabled && scope.obj.allowed" class="success check icon" :title="labels.allowListTitle"></i>
+              <i
+                v-if="allowListEnabled && scope.obj.allowed"
+                class="success check icon"
+                :title="labels.allowListTitle"
+              />
             </router-link>
           </td>
           <td>
@@ -64,33 +140,40 @@
             {{ scope.obj.outbox_activities_count }}
           </td>
           <td>
-            <human-date :date="scope.obj.creation_date"></human-date>
+            <human-date :date="scope.obj.creation_date" />
           </td>
           <td>
-            <span v-if="scope.obj.instance_policy"><i class="shield icon"></i> <translate translate-context="*/*/*">Yes</translate></span>
+            <span v-if="scope.obj.instance_policy"><i class="shield icon" /> <translate translate-context="*/*/*">Yes</translate></span>
           </td>
         </template>
       </action-table>
-      <div v-else class="ui placeholder segment">
+      <div
+        v-else
+        class="ui placeholder segment"
+      >
         <div class="ui icon header">
-          <i class="server icon"></i>
-          <translate translate-context="Content/Home/Placeholder">No other pods found</translate>
+          <i class="server icon" />
+          <translate translate-context="Content/Home/Placeholder">
+            No other pods found
+          </translate>
         </div>
       </div>
     </div>
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="selectPage"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-        ></pagination>
+        @page-changed="selectPage"
+      />
 
       <span v-if="result && result.results.length > 0">
-        <translate translate-context="Content/*/Paragraph"
-          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}">
+        <translate
+          translate-context="Content/*/Paragraph"
+          :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
+        >
           Showing results %{ start }-%{ end } on %{ total }
         </translate>
       </span>
@@ -108,17 +191,17 @@ import OrderingMixin from '@/components/mixins/Ordering'
 import TranslationsMixin from '@/components/mixins/Translations'
 
 export default {
-  mixins: [OrderingMixin, TranslationsMixin],
-  props: {
-    filters: {type: Object, required: false},
-    allowListEnabled: {type: Boolean, default: false},
-  },
   components: {
     Pagination,
     ActionTable
   },
+  mixins: [OrderingMixin, TranslationsMixin],
+  props: {
+    filters: { type: Object, required: false, default: function () { return {} } },
+    allowListEnabled: { type: Boolean, default: false }
+  },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
     return {
       time,
       isLoading: false,
@@ -138,45 +221,15 @@ export default {
 
     }
   },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    fetchData () {
-      let baseFilters = {
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search,
-        'ordering': this.getOrderingAsString(),
-      }
-      if (this.allowed !== null) {
-        baseFilters.allowed = this.allowed
-      }
-      let params = _.merge(baseFilters, this.filters)
-      let self = this
-      self.isLoading = true
-      self.checked = []
-      axios.get('/manage/federation/domains/', {params: params}).then((response) => {
-        self.result = response.data
-        self.isLoading = false
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    },
-    selectPage: function (page) {
-      this.page = page
-    }
-  },
   computed: {
     labels () {
       return {
         searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by name…'),
-        allowListTitle: this.$pgettext('Content/Moderation/Popup', 'This domain is present in your allow-list'),
+        allowListTitle: this.$pgettext('Content/Moderation/Popup', 'This domain is present in your allow-list')
       }
     },
     actionFilters () {
-      var currentFilters = {
+      const currentFilters = {
         q: this.search
       }
       if (this.filters) {
@@ -205,7 +258,7 @@ export default {
           filterCheckable: (obj) => {
             return obj.allowed
           }
-        },
+        }
       ]
     }
   },
@@ -226,6 +279,36 @@ export default {
     orderingDirection () {
       this.fetchData()
     }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      const baseFilters = {
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search,
+        ordering: this.getOrderingAsString()
+      }
+      if (this.allowed !== null) {
+        baseFilters.allowed = this.allowed
+      }
+      const params = _.merge(baseFilters, this.filters)
+      const self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/federation/domains/', { params: params }).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/moderation/InstancePolicyCard.vue b/front/src/components/manage/moderation/InstancePolicyCard.vue
index ec5bf1c58..ec68a3223 100644
--- a/front/src/components/manage/moderation/InstancePolicyCard.vue
+++ b/front/src/components/manage/moderation/InstancePolicyCard.vue
@@ -1,49 +1,83 @@
 <template>
   <div>
-    <slot></slot>
+    <slot />
     <p>
-      <i class="clock outline icon"></i><human-date :date="object.creation_date" /> &nbsp;
-      <i class="user icon"></i>{{ object.actor }}  &nbsp;
+      <i class="clock outline icon" /><human-date :date="object.creation_date" /> &nbsp;
+      <i class="user icon" />{{ object.actor }}  &nbsp;
       <template v-if="object.is_active">
-        <i class="play icon"></i>
-        <translate translate-context="*/*/*/State of feature">Enabled</translate>
+        <i class="play icon" />
+        <translate translate-context="*/*/*/State of feature">
+          Enabled
+        </translate>
       </template>
       <template v-if="!object.is_active">
-        <i class="pause icon"></i>
-        <translate translate-context="Content/Moderation/Card.List item">Paused</translate>
+        <i class="pause icon" />
+        <translate translate-context="Content/Moderation/Card.List item">
+          Paused
+        </translate>
       </template>
     </p>
     <div>
       <p><strong><translate translate-context="Content/Moderation/Card.Title/Noun">Rule</translate></strong></p>
       <p v-if="object.block_all">
-        <i class="ban icon"></i>
-        <translate translate-context="Content/Moderation/*/Verb">Block everything</translate>
+        <i class="ban icon" />
+        <translate translate-context="Content/Moderation/*/Verb">
+          Block everything
+        </translate>
       </p>
-      <div v-else class="ui list">
-        <div class="ui item" v-if="object.silence_activity">
-          <i class="feed icon"></i>
-          <div class="content"><translate translate-context="Content/Moderation/*/Verb">Mute activity</translate></div>
+      <div
+        v-else
+        class="ui list"
+      >
+        <div
+          v-if="object.silence_activity"
+          class="ui item"
+        >
+          <i class="feed icon" />
+          <div class="content">
+            <translate translate-context="Content/Moderation/*/Verb">
+              Mute activity
+            </translate>
+          </div>
         </div>
-        <div class="ui item" v-if="object.silence_notifications">
-          <i class="bell icon"></i>
-          <div class="content"><translate translate-context="Content/Moderation/*/Verb">Mute notifications</translate></div>
+        <div
+          v-if="object.silence_notifications"
+          class="ui item"
+        >
+          <i class="bell icon" />
+          <div class="content">
+            <translate translate-context="Content/Moderation/*/Verb">
+              Mute notifications
+            </translate>
+          </div>
         </div>
-        <div class="ui item" v-if="object.reject_media">
-          <i class="file icon"></i>
-          <div class="content"><translate translate-context="Content/Moderation/*/Verb">Reject media</translate></div>
+        <div
+          v-if="object.reject_media"
+          class="ui item"
+        >
+          <i class="file icon" />
+          <div class="content">
+            <translate translate-context="Content/Moderation/*/Verb">
+              Reject media
+            </translate>
+          </div>
         </div>
-
       </div>
     </div>
     <div v-if="markdown && object.summary">
-      <div class="ui hidden divider"></div>
+      <div class="ui hidden divider" />
       <p><strong><translate translate-context="Content/Moderation/*/Noun">Reason</translate></strong></p>
-      <div v-html="markdown.makeHtml(object.summary)"></div>
+      <div v-html="markdown.makeHtml(object.summary)" />
     </div>
-    <div class="ui hidden divider"></div>
-    <button @click="$emit('update')" class="ui right floated labeled icon button">
-      <i class="edit icon"></i>
-      <translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
+    <div class="ui hidden divider" />
+    <button
+      class="ui right floated labeled icon button"
+      @click="$emit('update')"
+    >
+      <i class="edit icon" />
+      <translate translate-context="Content/*/Button.Label/Verb">
+        Edit
+      </translate>
     </button>
   </div>
 </template>
@@ -52,7 +86,7 @@
 
 export default {
   props: {
-    object: {type: Object, default: null},
+    object: { type: Object, default: null }
   },
   data () {
     return {
@@ -60,9 +94,9 @@ export default {
     }
   },
   created () {
-    let self = this
+    const self = this
     import(/* webpackChunkName: "showdown" */ 'showdown').then(module => {
-      self.markdown = new module.default.Converter({simplifiedAutoLink: true, openLinksInNewWindow: true})
+      self.markdown = new module.default.Converter({ simplifiedAutoLink: true, openLinksInNewWindow: true })
     })
   }
 }
diff --git a/front/src/components/manage/moderation/InstancePolicyForm.vue b/front/src/components/manage/moderation/InstancePolicyForm.vue
index 5fc072165..abb3df423 100644
--- a/front/src/components/manage/moderation/InstancePolicyForm.vue
+++ b/front/src/components/manage/moderation/InstancePolicyForm.vue
@@ -1,22 +1,65 @@
 <template>
-  <form class="ui form" @submit.prevent="createOrUpdate">
+  <form
+    class="ui form"
+    @submit.prevent="createOrUpdate"
+  >
     <h3 class="ui header">
-      <translate translate-context="Content/Moderation/Card.Title/Verb" v-if="object" key="1">Edit moderation rule</translate>
-      <translate translate-context="Content/Moderation/Card.Button.Label/Verb" v-else key="2">Add a new moderation rule</translate>
+      <translate
+        v-if="object"
+        key="1"
+        translate-context="Content/Moderation/Card.Title/Verb"
+      >
+        Edit moderation rule
+      </translate>
+      <translate
+        v-else
+        key="2"
+        translate-context="Content/Moderation/Card.Button.Label/Verb"
+      >
+        Add a new moderation rule
+      </translate>
     </h3>
-    <div v-if="errors && errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/Moderation/Error message.Title">Error while creating rule</translate></h4>
+    <div
+      v-if="errors && errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/Moderation/Error message.Title">
+          Error while creating rule
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
 
-    <div class="field" v-if="object">
+    <div
+      v-if="object"
+      class="field"
+    >
       <div class="ui toggle checkbox">
-        <input id="policy-is-active" v-model="current.isActive" type="checkbox">
+        <input
+          id="policy-is-active"
+          v-model="current.isActive"
+          type="checkbox"
+        >
         <label for="policy-is-active">
-          <translate translate-context="*/*/*/State of feature" v-if="current.isActive" key="1">Enabled</translate>
-          <translate translate-context="*/*/*/State of feature" v-else key="2">Disabled</translate>
+          <translate
+            v-if="current.isActive"
+            key="1"
+            translate-context="*/*/*/State of feature"
+          >Enabled</translate>
+          <translate
+            v-else
+            key="2"
+            translate-context="*/*/*/State of feature"
+          >Disabled</translate>
           <tooltip :content="labels.isActiveHelp" />
         </label>
       </div>
@@ -26,11 +69,20 @@
         <translate translate-context="Content/Moderation/*/Noun">Reason</translate>
         <tooltip :content="labels.summaryHelp" />
       </label>
-      <textarea name="policy-summary" id="policy-summary" rows="5" v-model="current.summary"></textarea>
+      <textarea
+        id="policy-summary"
+        v-model="current.summary"
+        name="policy-summary"
+        rows="5"
+      />
     </div>
     <div class="field">
       <div class="ui toggle checkbox">
-        <input id="policy-is-active" v-model="current.blockAll" type="checkbox">
+        <input
+          id="policy-is-active"
+          v-model="current.blockAll"
+          type="checkbox"
+        >
         <label for="policy-is-active">
           <translate translate-context="Content/Moderation/*/Verb">Block everything</translate>
           <tooltip :content="labels.blockAllHelp" />
@@ -38,36 +90,78 @@
       </div>
     </div>
     <div class="ui horizontal divider">
-      <translate translate-context="Content/Moderation/Card.Title">Or customize your rule</translate>
+      <translate translate-context="Content/Moderation/Card.Title">
+        Or customize your rule
+      </translate>
     </div>
-    <div v-for="config in fieldConfig" :class="['field']">
+    <div
+      v-for="(config, key) in fieldConfig"
+      :key="key"
+      :class="['field']"
+    >
       <div class="ui toggle checkbox">
-        <input :id="'policy-' + config.id" v-model="current[config.id]" type="checkbox">
+        <input
+          :id="'policy-' + config.id"
+          v-model="current[config.id]"
+          type="checkbox"
+        >
         <label :for="'policy-' + config.id">
-          <i :class="[config.icon, 'icon']"></i>
+          <i :class="[config.icon, 'icon']" />
           {{ labels[config.id].label }}
           <tooltip :content="labels[config.id].help" />
         </label>
       </div>
     </div>
-    <div class="ui hidden divider"></div>
-    <button @click.prevent="$emit('cancel')" class="ui basic left floated button">
-      <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+    <div class="ui hidden divider" />
+    <button
+      class="ui basic left floated button"
+      @click.prevent="$emit('cancel')"
+    >
+      <translate translate-context="*/*/Button.Label/Verb">
+        Cancel
+      </translate>
     </button>
-    <button :class="['ui', 'right', 'floated', 'success', {'disabled loading': isLoading}, 'button']" :disabled="isLoading">
-      <translate translate-context="Content/Moderation/Card.Button.Label/Verb" v-if="object" key="1">Update</translate>
-      <translate translate-context="Content/Moderation/Card.Button.Label/Verb" v-else key="2">Create</translate>
+    <button
+      :class="['ui', 'right', 'floated', 'success', {'disabled loading': isLoading}, 'button']"
+      :disabled="isLoading"
+    >
+      <translate
+        v-if="object"
+        key="1"
+        translate-context="Content/Moderation/Card.Button.Label/Verb"
+      >
+        Update
+      </translate>
+      <translate
+        v-else
+        key="2"
+        translate-context="Content/Moderation/Card.Button.Label/Verb"
+      >
+        Create
+      </translate>
     </button>
-    <dangerous-button v-if="object" class="ui right floated basic danger button" @confirm="remove">
-      <translate translate-context="*/*/*/Verb">Delete</translate>
+    <dangerous-button
+      v-if="object"
+      class="ui right floated basic danger button"
+      @confirm="remove"
+    >
+      <translate translate-context="*/*/*/Verb">
+        Delete
+      </translate>
       <p slot="modal-header">
-        <translate translate-context="Popup/Moderation/Title">Delete this moderation rule?</translate>
+        <translate translate-context="Popup/Moderation/Title">
+          Delete this moderation rule?
+        </translate>
       </p>
       <p slot="modal-content">
-        <translate translate-context="Popup/Moderation/Paragraph">This action is irreversible.</translate>
+        <translate translate-context="Popup/Moderation/Paragraph">
+          This action is irreversible.
+        </translate>
       </p>
       <div slot="modal-confirm">
-        <translate translate-context="Popup/Moderation/Button.Label/Verb">Delete moderation rule</translate>
+        <translate translate-context="Popup/Moderation/Button.Label/Verb">
+          Delete moderation rule
+        </translate>
       </div>
     </dangerous-button>
   </form>
@@ -79,12 +173,12 @@ import _ from '@/lodash'
 
 export default {
   props: {
-    type: {type: String, required: true},
-    object: {type: Object, default: null},
-    target: {type: String, required: true},
+    type: { type: String, required: true },
+    object: { type: Object, default: null },
+    target: { type: String, required: true }
   },
   data () {
-    let current = this.object || {}
+    const current = this.object || {}
     return {
       isLoading: false,
       errors: [],
@@ -94,13 +188,13 @@ export default {
         blockAll: _.get(current, 'block_all', true),
         silenceActivity: _.get(current, 'silence_activity', false),
         silenceNotifications: _.get(current, 'silence_notifications', false),
-        rejectMedia: _.get(current, 'reject_media', false),
+        rejectMedia: _.get(current, 'reject_media', false)
       },
       fieldConfig: [
         // we hide those until we actually have the related features implemented :)
         // {id: "silenceActivity", icon: "feed"},
         // {id: "silenceNotifications", icon: "bell"},
-        {id: "rejectMedia", icon: "file"},
+        { id: 'rejectMedia', icon: 'file' }
       ]
     }
   },
@@ -108,30 +202,55 @@ export default {
     labels () {
       return {
         summaryHelp: this.$pgettext('Content/Moderation/Help text', "Explain why you're applying this policy: this will help you remember why you added this rule. Depending on your pod configuration, this may be displayed publicly to help users understand the moderation rules in place."),
-        isActiveHelp: this.$pgettext('Content/Moderation/Help text', "Use this setting to temporarily enable/disable the policy without completely removing it."),
-        blockAllHelp: this.$pgettext('Content/Moderation/Help text', "Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)"),
+        isActiveHelp: this.$pgettext('Content/Moderation/Help text', 'Use this setting to temporarily enable/disable the policy without completely removing it.'),
+        blockAllHelp: this.$pgettext('Content/Moderation/Help text', 'Block everything from this account or domain. This will prevent any interaction with the entity, and purge related content (uploads, libraries, follows, etc.)'),
         silenceActivity: {
-          help: this.$pgettext('Content/Moderation/Help text', "Hide account or domain content, except from followers."),
-          label: this.$pgettext('Content/Moderation/*/Verb', "Mute activity"),
+          help: this.$pgettext('Content/Moderation/Help text', 'Hide account or domain content, except from followers.'),
+          label: this.$pgettext('Content/Moderation/*/Verb', 'Mute activity')
         },
         silenceNotifications: {
-          help: this.$pgettext('Content/Moderation/Help text', "Prevent account or domain from triggering notifications, except from followers."),
-          label: this.$pgettext('Content/Moderation/*/Verb', "Mute notifications"),
+          help: this.$pgettext('Content/Moderation/Help text', 'Prevent account or domain from triggering notifications, except from followers.'),
+          label: this.$pgettext('Content/Moderation/*/Verb', 'Mute notifications')
         },
         rejectMedia: {
-          help: this.$pgettext('Content/Moderation/Help text', "Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well."),
-          label: this.$pgettext('Content/Moderation/*/Verb', "Reject media"),
+          help: this.$pgettext('Content/Moderation/Help text', 'Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well.'),
+          label: this.$pgettext('Content/Moderation/*/Verb', 'Reject media')
         }
       }
     }
   },
+  watch: {
+    'current.silenceActivity': function (v) {
+      if (v) {
+        this.current.blockAll = false
+      }
+    },
+    'current.silenceNotifications': function (v) {
+      if (v) {
+        this.current.blockAll = false
+      }
+    },
+    'current.rejectMedia': function (v) {
+      if (v) {
+        this.current.blockAll = false
+      }
+    },
+    'current.blockAll': function (v) {
+      if (v) {
+        const self = this
+        this.fieldConfig.forEach((f) => {
+          self.current[f.id] = false
+        })
+      }
+    }
+  },
   methods: {
     createOrUpdate () {
-      let self = this
+      const self = this
       this.isLoading = true
       this.errors = []
       let url, method
-      let data = {
+      const data = {
         summary: this.current.summary,
         is_active: this.current.isActive,
         block_all: this.current.blockAll,
@@ -140,14 +259,14 @@ export default {
         reject_media: this.current.rejectMedia,
         target: {
           type: this.type,
-          id: this.target,
+          id: this.target
         }
       }
       if (this.object) {
         url = `manage/moderation/instance-policies/${this.object.id}/`
         method = 'patch'
       } else {
-        url = `manage/moderation/instance-policies/`
+        url = 'manage/moderation/instance-policies/'
         method = 'post'
       }
       axios[method](url, data).then((response) => {
@@ -159,11 +278,11 @@ export default {
       })
     },
     remove () {
-      let self = this
+      const self = this
       this.isLoading = true
       this.errors = []
 
-      let url = `manage/moderation/instance-policies/${this.object.id}/`
+      const url = `manage/moderation/instance-policies/${this.object.id}/`
       axios.delete(url).then((response) => {
         this.isLoading = false
         self.$emit('delete')
@@ -172,31 +291,6 @@ export default {
         self.errors = error.backendErrors
       })
     }
-  },
-  watch: {
-    'current.silenceActivity': function (v) {
-      if (v) {
-        this.current.blockAll = false
-      }
-    },
-    'current.silenceNotifications': function (v) {
-      if (v) {
-        this.current.blockAll = false
-      }
-    },
-    'current.rejectMedia': function (v) {
-      if (v) {
-        this.current.blockAll = false
-      }
-    },
-    'current.blockAll': function (v) {
-      if (v) {
-        let self = this
-        this.fieldConfig.forEach((f) => {
-          self.current[f.id] = false
-        })
-      }
-    }
   }
 }
 </script>
diff --git a/front/src/components/manage/moderation/InstancePolicyModal.vue b/front/src/components/manage/moderation/InstancePolicyModal.vue
index b09d0d322..555c0df64 100644
--- a/front/src/components/manage/moderation/InstancePolicyModal.vue
+++ b/front/src/components/manage/moderation/InstancePolicyModal.vue
@@ -1,60 +1,84 @@
 <template>
-  <button class="ui button" @click.prevent="show = !show">
-    <i class="shield icon"></i>&nbsp;
+  <button
+    class="ui button"
+    @click.prevent="show = !show"
+  >
+    <i class="shield icon" />&nbsp;
     <slot>
-      <translate translate-context="Content/Moderation/Button.Label">Moderation rules…</translate>
+      <translate translate-context="Content/Moderation/Button.Label">
+        Moderation rules…
+      </translate>
     </slot>
-    <modal :show.sync="show" @show="fetchData">
+    <modal
+      :show.sync="show"
+      @show="fetchData"
+    >
       <h4 class="header">
-        <translate :translate-params="{obj: target}" translate-context="Popup/Moderation/Title/Verb">Manage moderation rules for %{ obj }</translate>
+        <translate
+          :translate-params="{obj: target}"
+          translate-context="Popup/Moderation/Title/Verb"
+        >
+          Manage moderation rules for %{ obj }
+        </translate>
       </h4>
       <div class="content">
         <div class="description">
-          <div v-if="isLoading" class="ui active loader"></div>
-          <instance-policy-card v-else-if="obj && !showForm" :object="obj" @update="showForm = true">
+          <div
+            v-if="isLoading"
+            class="ui active loader"
+          />
+          <instance-policy-card
+            v-else-if="obj && !showForm"
+            :object="obj"
+            @update="showForm = true"
+          >
             <header class="ui header">
               <h3>
-                <translate translate-context="Content/Moderation/Card.Title">This entity is subject to specific moderation rules</translate>
+                <translate translate-context="Content/Moderation/Card.Title">
+                  This entity is subject to specific moderation rules
+                </translate>
               </h3>
             </header>
-            </instance-policy-card>
-            <instance-policy-form
-              v-else
-              @cancel="showForm = false"
-              @save="showForm = false; result = {count: 1, results: [$event]}"
-              @delete="result = {count: 0, results: []}; showForm = false"
-              :object="obj"
-              :type="type"
-              :target="target" />
+          </instance-policy-card>
+          <instance-policy-form
+            v-else
+            :object="obj"
+            :type="type"
+            :target="target"
+            @cancel="showForm = false"
+            @save="showForm = false; result = {count: 1, results: [$event]}"
+            @delete="result = {count: 0, results: []}; showForm = false"
+          />
         </div>
-        <div class="ui hidden divider"></div>
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
+        <div class="ui hidden divider" />
       </div>
       <div class="actions">
         <button class="ui deny button">
-          <translate translate-context="*/*/Button.Label/Verb">Close</translate>
+          <translate translate-context="*/*/Button.Label/Verb">
+            Close
+          </translate>
         </button>
       </div>
     </modal>
-
   </button>
 </template>
 
 <script>
 import axios from 'axios'
-import InstancePolicyForm from "@/components/manage/moderation/InstancePolicyForm"
-import InstancePolicyCard from "@/components/manage/moderation/InstancePolicyCard"
+import InstancePolicyForm from '@/components/manage/moderation/InstancePolicyForm'
+import InstancePolicyCard from '@/components/manage/moderation/InstancePolicyCard'
 import Modal from '@/components/semantic/Modal'
 
 export default {
-  props: {
-    target: {required: true},
-    type: {required: true},
-  },
   components: {
     InstancePolicyForm,
     InstancePolicyCard,
-    Modal,
+    Modal
+  },
+  props: {
+    target: { type: String, required: true },
+    type: { type: String, required: true }
   },
   data () {
     return {
@@ -62,7 +86,7 @@ export default {
       isLoading: false,
       errors: [],
       showForm: false,
-      result: null,
+      result: null
     }
   },
   computed: {
@@ -75,18 +99,18 @@ export default {
   },
   methods: {
     fetchData () {
-      let params = {}
+      const params = {}
       if (this.type === 'domain') {
         params.target_domain = this.target
       }
       if (this.type === 'actor') {
-        let parts = this.target.split('@')
+        const parts = this.target.split('@')
         params.target_account_username = parts[0]
         params.target_account_domain = parts[1]
       }
-      let self = this
+      const self = this
       self.isLoading = true
-      axios.get('/manage/moderation/instance-policies/', {params: params}).then((response) => {
+      axios.get('/manage/moderation/instance-policies/', { params: params }).then((response) => {
         self.result = response.data
         self.isLoading = false
       }, error => {
diff --git a/front/src/components/manage/moderation/NoteForm.vue b/front/src/components/manage/moderation/NoteForm.vue
index d22bfd916..20632c8dd 100644
--- a/front/src/components/manage/moderation/NoteForm.vue
+++ b/front/src/components/manage/moderation/NoteForm.vue
@@ -1,16 +1,44 @@
 <template>
-  <form class="ui form" @submit.prevent="submit()">
-    <div v-if="errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/Moderation/Error message.Title">Error while submitting note</translate></h4>
+  <form
+    class="ui form"
+    @submit.prevent="submit()"
+  >
+    <div
+      v-if="errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/Moderation/Error message.Title">
+          Error while submitting note
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
     <div class="field">
-      <content-form field-id="change-summary" :required="true" v-model="summary" :rows="3" :placeholder="labels.summaryPlaceholder"></content-form>
+      <content-form
+        v-model="summary"
+        field-id="change-summary"
+        :required="true"
+        :rows="3"
+        :placeholder="labels.summaryPlaceholder"
+      />
     </div>
-    <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']" type="submit" :disabled="isLoading">
-      <translate translate-context="Content/Moderation/Button.Label/Verb">Add note</translate>
+    <button
+      :class="['ui', {'loading': isLoading}, 'right', 'floated', 'button']"
+      type="submit"
+      :disabled="isLoading"
+    >
+      <translate translate-context="Content/Moderation/Button.Label/Verb">
+        Add note
+      </translate>
     </button>
   </form>
 </template>
@@ -21,33 +49,33 @@ import showdown from 'showdown'
 
 export default {
   props: {
-    target: {required: true},
+    target: { type: String, required: true }
   },
   data () {
-      return {
+    return {
       markdown: new showdown.Converter(),
       isLoading: false,
       summary: '',
-      errors: [],
+      errors: []
     }
   },
   computed: {
     labels () {
       return {
-        summaryPlaceholder: this.$pgettext('Content/Moderation/Placeholder', 'Describe what actions have been taken, or any other related updates…'),
+        summaryPlaceholder: this.$pgettext('Content/Moderation/Placeholder', 'Describe what actions have been taken, or any other related updates…')
       }
-    },
+    }
   },
   methods: {
     submit () {
-      let self = this
+      const self = this
       this.isLoading = true
-      let payload = {
+      const payload = {
         target: this.target,
         summary: this.summary
       }
       this.errors = []
-      axios.post(`manage/moderation/notes/`, payload).then((response) => {
+      axios.post('manage/moderation/notes/', payload).then((response) => {
         self.$emit('created', response.data)
         self.summary = ''
         self.isLoading = false
@@ -55,7 +83,7 @@ export default {
         self.errors = error.backendErrors
         self.isLoading = false
       })
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/moderation/NotesThread.vue b/front/src/components/manage/moderation/NotesThread.vue
index 9c2d75700..69fcb5f6f 100644
--- a/front/src/components/manage/moderation/NotesThread.vue
+++ b/front/src/components/manage/moderation/NotesThread.vue
@@ -1,32 +1,54 @@
 <template>
   <div class="ui feed">
-    <div class="event" v-for="note in notes" :key="note.uuid">
+    <div
+      v-for="note in notes"
+      :key="note.uuid"
+      class="event"
+    >
       <div class="label">
-        <i class="comment outline icon"></i>
+        <i class="comment outline icon" />
       </div>
       <div class="content">
         <div class="summary">
-          <actor-link :admin="true" :actor="note.author"></actor-link>
+          <actor-link
+            :admin="true"
+            :actor="note.author"
+          />
           <div class="date">
-            <human-date :date="note.creation_date"></human-date>
+            <human-date :date="note.creation_date" />
           </div>
         </div>
         <div class="extra text">
           <expandable-div :content="note.summary">
-            <div v-html="markdown.makeHtml(note.summary)"></div>
+            <div v-html="markdown.makeHtml(note.summary)" />
           </expandable-div>
         </div>
         <div class="meta">
           <dangerous-button
             :class="['ui', {loading: isLoading}, 'basic borderless mini button']"
-            @confirm="remove(note)">
-            <i class="trash icon"></i>
-            <translate translate-context="*/*/*/Verb">Delete</translate>
-            <p slot="modal-header"><translate translate-context="Popup/Moderation/Title">Delete this note?</translate></p>
+            @confirm="remove(note)"
+          >
+            <i class="trash icon" />
+            <translate translate-context="*/*/*/Verb">
+              Delete
+            </translate>
+            <p slot="modal-header">
+              <translate translate-context="Popup/Moderation/Title">
+                Delete this note?
+              </translate>
+            </p>
             <div slot="modal-content">
-              <p><translate translate-context="Content/Moderation/Paragraph">The note will be removed. This action is irreversible.</translate></p>
+              <p>
+                <translate translate-context="Content/Moderation/Paragraph">
+                  The note will be removed. This action is irreversible.
+                </translate>
+              </p>
             </div>
-            <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p>
+            <p slot="modal-confirm">
+              <translate translate-context="*/*/*/Verb">
+                Delete
+              </translate>
+            </p>
           </dangerous-button>
         </div>
       </div>
@@ -40,25 +62,25 @@ import showdown from 'showdown'
 
 export default {
   props: {
-    notes: {required: true},
+    notes: { type: String, required: true }
   },
   data () {
-      return {
+    return {
       markdown: new showdown.Converter(),
-      isLoading: false,
+      isLoading: false
     }
   },
   methods: {
     remove (obj) {
-      let self = this
+      const self = this
       this.isLoading = true
       axios.delete(`manage/moderation/notes/${obj.uuid}/`).then((response) => {
         self.$emit('deleted', obj.uuid)
         self.isLoading = false
-      }, error => {
+      }, () => {
         self.isLoading = false
       })
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/moderation/ReportCard.vue b/front/src/components/manage/moderation/ReportCard.vue
index 5e6b1605b..d2dd0a54c 100644
--- a/front/src/components/manage/moderation/ReportCard.vue
+++ b/front/src/components/manage/moderation/ReportCard.vue
@@ -3,48 +3,69 @@
     <div class="content">
       <h4 class="header">
         <router-link :to="{name: 'manage.moderation.reports.detail', params: {id: obj.uuid}}">
-          <translate translate-context="Content/Moderation/Card/Short" :translate-params="{id: obj.uuid.substring(0, 8)}">Report %{ id }</translate>
+          <translate
+            translate-context="Content/Moderation/Card/Short"
+            :translate-params="{id: obj.uuid.substring(0, 8)}"
+          >
+            Report %{ id }
+          </translate>
         </router-link>
-        <collapse-link class="right floated" v-model="isCollapsed"></collapse-link>
+        <collapse-link
+          v-model="isCollapsed"
+          class="right floated"
+        />
       </h4>
       <div class="content">
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
         <div class="ui stackable two column grid">
           <div class="column">
             <table class="ui very basic unstackable table">
               <tbody>
                 <tr>
                   <td>
-                    <translate translate-context="Content/Moderation/*">Submitted by</translate>
+                    <translate translate-context="Content/Moderation/*">
+                      Submitted by
+                    </translate>
                   </td>
                   <td>
                     <div v-if="obj.submitter">
-                      <actor-link :admin="true" :actor="obj.submitter" />
+                      <actor-link
+                        :admin="true"
+                        :actor="obj.submitter"
+                      />
                     </div>
-                    <div v-else="obj.submitter_email">
+                    <div v-else-if="obj.submitter_email">
                       {{ obj.submitter_email }}
                     </div>
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="*/*/*">Category</translate>
+                    <translate translate-context="*/*/*">
+                      Category
+                    </translate>
                   </td>
                   <td>
                     <report-category-dropdown
                       :value="obj.type"
-                      @input="update({type: $event})">
+                      @input="update({type: $event})"
+                    >
                       &#32;
-                      <action-feedback :is-loading="updating.type"></action-feedback>
+                      <action-feedback :is-loading="updating.type" />
                     </report-category-dropdown>
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Content/*/*/Noun">Creation date</translate>
+                    <translate translate-context="Content/*/*/Noun">
+                      Creation date
+                    </translate>
                   </td>
                   <td>
-                    <human-date :date="obj.creation_date" :icon="true"></human-date>
+                    <human-date
+                      :date="obj.creation_date"
+                      :icon="true"
+                    />
                   </td>
                 </tr>
               </tbody>
@@ -55,45 +76,72 @@
               <tbody>
                 <tr>
                   <td>
-                    <translate translate-context="*/*/*">Status</translate>
+                    <translate translate-context="*/*/*">
+                      Status
+                    </translate>
                   </td>
                   <td v-if="obj.is_handled">
                     <span v-if="obj.is_handled">
-                      <i class="success check icon"></i>
+                      <i class="success check icon" />
                       <translate translate-context="Content/*/*/Short">Resolved</translate>
                     </span>
                   </td>
                   <td v-else>
-                    <i class="danger x icon"></i>
-                    <translate translate-context="Content/*/*/Short">Unresolved</translate>
+                    <i class="danger x icon" />
+                    <translate translate-context="Content/*/*/Short">
+                      Unresolved
+                    </translate>
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Content/Moderation/*">Assigned to</translate>
+                    <translate translate-context="Content/Moderation/*">
+                      Assigned to
+                    </translate>
                   </td>
                   <td>
                     <div v-if="obj.assigned_to">
-                      <actor-link :admin="true" :actor="obj.assigned_to" />
+                      <actor-link
+                        :admin="true"
+                        :actor="obj.assigned_to"
+                      />
                     </div>
-                    <translate v-else translate-context="*/*/*">N/A</translate>
+                    <translate
+                      v-else
+                      translate-context="*/*/*"
+                    >
+                      N/A
+                    </translate>
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Content/*/*/Noun">Resolution date</translate>
+                    <translate translate-context="Content/*/*/Noun">
+                      Resolution date
+                    </translate>
                   </td>
                   <td>
-                    <human-date v-if="obj.handled_date" :date="obj.handled_date" :icon="true"></human-date>
-                    <translate v-else translate-context="*/*/*">N/A</translate>
+                    <human-date
+                      v-if="obj.handled_date"
+                      :date="obj.handled_date"
+                      :icon="true"
+                    />
+                    <translate
+                      v-else
+                      translate-context="*/*/*"
+                    >
+                      N/A
+                    </translate>
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Content/*/*/Noun">Internal notes</translate>
+                    <translate translate-context="Content/*/*/Noun">
+                      Internal notes
+                    </translate>
                   </td>
                   <td>
-                    <i class="comment icon"></i>
+                    <i class="comment icon" />
                     {{ obj.notes.length }}
                   </td>
                 </tr>
@@ -103,95 +151,165 @@
         </div>
       </div>
     </div>
-    <div class="main content" v-if="!isCollapsed">
+    <div
+      v-if="!isCollapsed"
+      class="main content"
+    >
       <div class="ui stackable two column grid">
         <div class="column">
           <h3>
-            <translate translate-context="*/*/Field.Label/Noun">Message</translate>
+            <translate translate-context="*/*/Field.Label/Noun">
+              Message
+            </translate>
           </h3>
-          <expandable-div v-if="obj.summary" class="summary" :content="obj.summary">
-            <div v-html="markdown.makeHtml(obj.summary)"></div>
+          <expandable-div
+            v-if="obj.summary"
+            class="summary"
+            :content="obj.summary"
+          >
+            <div v-html="markdown.makeHtml(obj.summary)" />
           </expandable-div>
         </div>
         <aside class="column">
           <h3>
-            <translate translate-context="Content/*/*/Short">Reported object</translate>
+            <translate translate-context="Content/*/*/Short">
+              Reported object
+            </translate>
           </h3>
-          <div v-if="!obj.target" role="alert" class="ui warning message">
-            <translate translate-context="Content/Moderation/Message">The object associated with this report was deleted.</translate>
+          <div
+            v-if="!obj.target"
+            role="alert"
+            class="ui warning message"
+          >
+            <translate translate-context="Content/Moderation/Message">
+              The object associated with this report was deleted.
+            </translate>
           </div>
-          <router-link class="ui basic button" v-if="target && configs[target.type].urls.getDetail" :to="configs[target.type].urls.getDetail(obj.target_state)">
-            <i class="eye icon"></i>
-            <translate translate-context="Content/Moderation/Link">View public page</translate>
+          <router-link
+            v-if="target && configs[target.type].urls.getDetail"
+            class="ui basic button"
+            :to="configs[target.type].urls.getDetail(obj.target_state)"
+          >
+            <i class="eye icon" />
+            <translate translate-context="Content/Moderation/Link">
+              View public page
+            </translate>
           </router-link>
-          <router-link class="ui basic button" v-if="target && configs[target.type].urls.getAdminDetail" :to="configs[target.type].urls.getAdminDetail(obj.target_state)">
-            <i class="wrench icon"></i>
-            <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
+          <router-link
+            v-if="target && configs[target.type].urls.getAdminDetail"
+            class="ui basic button"
+            :to="configs[target.type].urls.getAdminDetail(obj.target_state)"
+          >
+            <i class="wrench icon" />
+            <translate translate-context="Content/Moderation/Link">
+              Open in moderation interface
+            </translate>
           </router-link>
           <table class="ui very basic unstackable table">
             <tbody>
               <tr v-if="target">
                 <td>
-                  <translate translate-context="Content/Track/Table.Label/Noun">Type</translate>
+                  <translate translate-context="Content/Track/Table.Label/Noun">
+                    Type
+                  </translate>
                 </td>
                 <td colspan="2">
-                  <i :class="[configs[target.type].icon, 'icon']"></i>
+                  <i :class="[configs[target.type].icon, 'icon']" />
                   {{ configs[target.type].label }}
                 </td>
               </tr>
               <tr v-if="obj.target_owner && (!target || target.type !== 'account')">
                 <td>
-                  <translate translate-context="*/*/*">Owner</translate>
+                  <translate translate-context="*/*/*">
+                    Owner
+                  </translate>
                 </td>
                 <td>
-                  <actor-link :admin="true" :actor="obj.target_owner"></actor-link>
+                  <actor-link
+                    :admin="true"
+                    :actor="obj.target_owner"
+                  />
                 </td>
                 <td>
                   <instance-policy-modal
                     v-if="!obj.target_owner.is_local"
-                    class="right floated mini basic" type="actor" :target="obj.target_owner.full_username" />
+                    class="right floated mini basic"
+                    type="actor"
+                    :target="obj.target_owner.full_username"
+                  />
                 </td>
               </tr>
               <tr v-if="target && target.type === 'account'">
                 <td>
-                  <translate translate-context="*/*/*/Noun">Account</translate>
+                  <translate translate-context="*/*/*/Noun">
+                    Account
+                  </translate>
                 </td>
                 <td>
-                  <actor-link :admin="true" :actor="obj.target_owner"></actor-link>
+                  <actor-link
+                    :admin="true"
+                    :actor="obj.target_owner"
+                  />
                 </td>
                 <td>
                   <instance-policy-modal
                     v-if="!obj.target_owner.is_local"
-                    class="right floated mini basic" type="actor" :target="obj.target_owner.full_username" />
+                    class="right floated mini basic"
+                    type="actor"
+                    :target="obj.target_owner.full_username"
+                  />
                 </td>
               </tr>
               <tr v-if="obj.target_state.is_local">
                 <td>
-                  <translate translate-context="Content/Moderation/*/Noun">Domain</translate>
+                  <translate translate-context="Content/Moderation/*/Noun">
+                    Domain
+                  </translate>
                 </td>
                 <td colspan="2">
-                  <i class="home icon"></i>
-                  <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
+                  <i class="home icon" />
+                  <translate translate-context="Content/Moderation/*/Short, Noun">
+                    Local
+                  </translate>
                 </td>
               </tr>
               <tr v-else-if="obj.target_state.domain">
                 <td>
                   <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: obj.target_state.domain }}">
-                    <translate translate-context="Content/Moderation/*/Noun">Domain</translate>
+                    <translate translate-context="Content/Moderation/*/Noun">
+                      Domain
+                    </translate>
                   </router-link>
                 </td>
                 <td>
                   {{ obj.target_state.domain }}
                 </td>
                 <td>
-                  <instance-policy-modal class="right floated mini basic" type="domain" :target="obj.target_state.domain" />
+                  <instance-policy-modal
+                    class="right floated mini basic"
+                    type="domain"
+                    :target="obj.target_state.domain"
+                  />
                 </td>
               </tr>
-              <tr v-for="field in targetFields" :key="field.id">
+              <tr
+                v-for="field in targetFields"
+                :key="field.id"
+              >
                 <td>{{ field.label }}</td>
-                <td colspan="2" v-if="field.repr">{{ field.repr }}</td>
-                <td colspan="2" v-else>
-                  <translate translate-context="*/*/*">N/A</translate>
+                <td
+                  v-if="field.repr"
+                  colspan="2"
+                >
+                  {{ field.repr }}
+                </td>
+                <td
+                  v-else
+                  colspan="2"
+                >
+                  <translate translate-context="*/*/*">
+                    N/A
+                  </translate>
                 </td>
               </tr>
             </tbody>
@@ -201,42 +319,66 @@
       <div class="ui stackable two column grid">
         <div class="column">
           <h3>
-            <translate translate-context="Content/*/*/Noun">Internal notes</translate>
+            <translate translate-context="Content/*/*/Noun">
+              Internal notes
+            </translate>
           </h3>
-          <notes-thread @deleted="handleRemovedNote($event)" :notes="obj.notes" />
-          <note-form @created="obj.notes.push($event)" :target="{type: 'report', uuid: obj.uuid}" />
+          <notes-thread
+            :notes="obj.notes"
+            @deleted="handleRemovedNote($event)"
+          />
+          <note-form
+            :target="{type: 'report', uuid: obj.uuid}"
+            @created="obj.notes.push($event)"
+          />
         </div>
         <div class="column">
           <h3>
-            <translate translate-context="Content/*/*/Noun">Actions</translate>
+            <translate translate-context="Content/*/*/Noun">
+              Actions
+            </translate>
           </h3>
           <div class="ui labelled icon basic buttons">
             <button
               v-if="obj.is_handled === false"
+              :class="['ui', {loading: isLoading}, 'button']"
               @click="resolve(true)"
-              :class="['ui', {loading: isLoading}, 'button']">
-              <i class="success check icon"></i>&nbsp;
-              <translate translate-context="Content/*/Button.Label/Verb">Resolve</translate>
+            >
+              <i class="success check icon" />&nbsp;
+              <translate translate-context="Content/*/Button.Label/Verb">
+                Resolve
+              </translate>
             </button>
             <button
               v-if="obj.is_handled === true"
+              :class="['ui', {loading: isLoading}, 'button']"
               @click="resolve(false)"
-              :class="['ui', {loading: isLoading}, 'button']">
-              <i class="warning redo icon"></i>&nbsp;
-              <translate translate-context="Content/*/Button.Label">Unresolve</translate>
+            >
+              <i class="warning redo icon" />&nbsp;
+              <translate translate-context="Content/*/Button.Label">
+                Unresolve
+              </translate>
             </button>
-            <template v-for="action in actions">
+            <template
+              v-for="(action, key) in actions"
+            >
               <dangerous-button
                 v-if="action.dangerous && action.show(obj)"
+                :key="key"
                 :class="['ui', {loading: isLoading}, 'button']"
-                :action="action.handler">
-                <i :class="[action.iconColor, action.icon, 'icon']"></i>&nbsp;
+                :action="action.handler"
+              >
+                <i :class="[action.iconColor, action.icon, 'icon']" />&nbsp;
                 {{ action.label }}
-                <p slot="modal-header">{{ action.modalHeader}}</p>
+                <p slot="modal-header">
+                  {{ action.modalHeader }}
+                </p>
                 <div slot="modal-content">
                   <p>{{ action.modalContent }}</p>
                 </div>
-                <p slot="modal-confirm">{{ action.modalConfirmLabel }}</p>
+                <p slot="modal-confirm">
+                  {{ action.modalConfirmLabel }}
+                </p>
               </dangerous-button>
             </template>
           </div>
@@ -248,16 +390,14 @@
 
 <script>
 import axios from 'axios'
-import { diffWordsWithSpace } from 'diff'
 import NoteForm from '@/components/manage/moderation/NoteForm'
 import NotesThread from '@/components/manage/moderation/NotesThread'
 import ReportCategoryDropdown from '@/components/moderation/ReportCategoryDropdown'
 import InstancePolicyModal from '@/components/manage/moderation/InstancePolicyModal'
 import entities from '@/entities'
-import {setUpdate} from '@/utils'
+import { setUpdate } from '@/utils'
 import showdown from 'showdown'
 
-
 function castValue (value) {
   if (value === null || value === undefined) {
     return ''
@@ -266,23 +406,24 @@ function castValue (value) {
 }
 
 export default {
-  props: {
-    obj: {required: true},
-    currentState: {required: false}
-  },
   components: {
     NoteForm,
     NotesThread,
     ReportCategoryDropdown,
-    InstancePolicyModal,
+    InstancePolicyModal
+  },
+  props: {
+    initObj: { type: Object, required: true },
+    currentState: { type: String, required: false, default: '' }
   },
   data () {
     return {
+      obj: this.initObj,
       markdown: new showdown.Converter(),
       isLoading: false,
       isCollapsed: false,
       updating: {
-        type: false,
+        type: false
       }
     }
   },
@@ -303,7 +444,7 @@ export default {
         return ''
       }
       let namespace
-      let id = this.target.id
+      const id = this.target.id
       if (this.target.type === 'track') {
         namespace = 'library.tracks.edit.detail'
       }
@@ -313,24 +454,23 @@ export default {
       if (this.target.type === 'artist') {
         namespace = 'library.artists.edit.detail'
       }
-      return this.$router.resolve({name: namespace, params: {id, editId: this.obj.uuid}}).href
+      return this.$router.resolve({ name: namespace, params: { id, editId: this.obj.uuid } }).href
     },
 
     targetFields () {
       if (!this.target) {
         return []
       }
-      let payload = this.obj.target_state
-      let fields = this.configs[this.target.type].moderatedFields
-      let self = this
+      const payload = this.obj.target_state
+      const fields = this.configs[this.target.type].moderatedFields
       return fields.map((fieldConfig) => {
-        let dummyRepr = (v) => { return v }
-        let getValueRepr = fieldConfig.getValueRepr || dummyRepr
-        let d = {
+        const dummyRepr = (v) => { return v }
+        const getValueRepr = fieldConfig.getValueRepr || dummyRepr
+        const d = {
           id: fieldConfig.id,
           label: fieldConfig.label,
           value: payload[fieldConfig.id],
-          repr: castValue(getValueRepr(payload[fieldConfig.id])),
+          repr: castValue(getValueRepr(payload[fieldConfig.id]))
         }
         return d
       })
@@ -346,11 +486,11 @@ export default {
       if (!this.target) {
         return []
       }
-      let self = this
-      let actions = []
-      let typeConfig = this.configs[this.target.type]
+      const self = this
+      const actions = []
+      const typeConfig = this.configs[this.target.type]
       if (typeConfig.getDeleteUrl) {
-        let deleteUrl = typeConfig.getDeleteUrl(this.target)
+        const deleteUrl = typeConfig.getDeleteUrl(this.target)
         actions.push({
           label: this.$pgettext('Content/Moderation/Button/Verb', 'Delete reported object'),
           modalHeader: this.$pgettext('Content/Moderation/Popup/Header', 'Delete reported object?'),
@@ -365,7 +505,7 @@ export default {
               console.log('Target deleted')
               self.obj.target = null
               self.resolve(true)
-            }, error => {
+            }, () => {
               console.log('Error while deleting target')
             })
           }
@@ -376,8 +516,8 @@ export default {
   },
   methods: {
     update (payload) {
-      let url = `manage/moderation/reports/${this.obj.uuid}/`
-      let self = this
+      const url = `manage/moderation/reports/${this.obj.uuid}/`
+      const self = this
       this.isLoading = true
       setUpdate(payload, this.updating, true)
       axios.patch(url, payload).then((response) => {
@@ -385,16 +525,16 @@ export default {
         Object.assign(self.obj, payload)
         self.isLoading = false
         setUpdate(payload, self.updating, false)
-      }, error => {
+      }, () => {
         self.isLoading = false
         setUpdate(payload, self.updating, false)
       })
     },
     resolve (v) {
-      let url = `manage/moderation/reports/${this.obj.uuid}/`
-      let self = this
+      const url = `manage/moderation/reports/${this.obj.uuid}/`
+      const self = this
       this.isLoading = true
-      axios.patch(url, {is_handled: v}).then((response) => {
+      axios.patch(url, { is_handled: v }).then((response) => {
         self.$emit('handled', v)
         self.isLoading = false
         self.obj.is_handled = v
@@ -405,16 +545,16 @@ export default {
         } else {
           increment = 1
         }
-        self.$store.commit('ui/incrementNotifications', {count: increment, type: 'pendingReviewReports'})
-      }, error => {
+        self.$store.commit('ui/incrementNotifications', { count: increment, type: 'pendingReviewReports' })
+      }, () => {
         self.isLoading = false
       })
     },
     handleRemovedNote (uuid) {
       this.obj.notes = this.obj.notes.filter((note) => {
-        return note.uuid != uuid
+        return note.uuid !== uuid
       })
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/moderation/UserRequestCard.vue b/front/src/components/manage/moderation/UserRequestCard.vue
index 3528e0a56..8cf93a53b 100644
--- a/front/src/components/manage/moderation/UserRequestCard.vue
+++ b/front/src/components/manage/moderation/UserRequestCard.vue
@@ -3,30 +3,48 @@
     <div class="content">
       <h4 class="header">
         <router-link :to="{name: 'manage.moderation.requests.detail', params: {id: obj.uuid}}">
-          <translate translate-context="Content/Moderation/Card/Short" :translate-params="{id: obj.uuid.substring(0, 8)}">Request %{ id }</translate>
+          <translate
+            translate-context="Content/Moderation/Card/Short"
+            :translate-params="{id: obj.uuid.substring(0, 8)}"
+          >
+            Request %{ id }
+          </translate>
         </router-link>
-        <collapse-link class="right floated" v-model="isCollapsed"></collapse-link>
+        <collapse-link
+          v-model="isCollapsed"
+          class="right floated"
+        />
       </h4>
       <div class="content">
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
         <div class="ui stackable two column grid">
           <div class="column">
             <table class="ui very basic unstackable table">
               <tbody>
                 <tr>
                   <td>
-                    <translate translate-context="Content/Moderation/*">Submitted by</translate>
+                    <translate translate-context="Content/Moderation/*">
+                      Submitted by
+                    </translate>
                   </td>
                   <td>
-                    <actor-link :admin="true" :actor="obj.submitter" />
+                    <actor-link
+                      :admin="true"
+                      :actor="obj.submitter"
+                    />
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Content/*/*/Noun">Creation date</translate>
+                    <translate translate-context="Content/*/*/Noun">
+                      Creation date
+                    </translate>
                   </td>
                   <td>
-                    <human-date :date="obj.creation_date" :icon="true"></human-date>
+                    <human-date
+                      :date="obj.creation_date"
+                      :icon="true"
+                    />
                   </td>
                 </tr>
               </tbody>
@@ -37,49 +55,80 @@
               <tbody>
                 <tr>
                   <td>
-                    <translate translate-context="*/*/*">Status</translate>
+                    <translate translate-context="*/*/*">
+                      Status
+                    </translate>
                   </td>
                   <td>
                     <template v-if="obj.status === 'pending'">
-                      <i class="warning hourglass icon"></i>
-                      <translate translate-context="Content/Library/*/Short">Pending</translate>
+                      <i class="warning hourglass icon" />
+                      <translate translate-context="Content/Library/*/Short">
+                        Pending
+                      </translate>
                     </template>
                     <template v-else-if="obj.status === 'refused'">
-                      <i class="danger x icon"></i>
-                      <translate translate-context="Content/*/*/Short">Refused</translate>
+                      <i class="danger x icon" />
+                      <translate translate-context="Content/*/*/Short">
+                        Refused
+                      </translate>
                     </template>
                     <template v-else-if="obj.status === 'approved'">
-                      <i class="success check icon"></i>
-                      <translate translate-context="Content/*/*/Short">Approved</translate>
+                      <i class="success check icon" />
+                      <translate translate-context="Content/*/*/Short">
+                        Approved
+                      </translate>
                     </template>
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Content/Moderation/*">Assigned to</translate>
+                    <translate translate-context="Content/Moderation/*">
+                      Assigned to
+                    </translate>
                   </td>
                   <td>
                     <div v-if="obj.assigned_to">
-                      <actor-link :admin="true" :actor="obj.assigned_to" />
+                      <actor-link
+                        :admin="true"
+                        :actor="obj.assigned_to"
+                      />
                     </div>
-                    <translate v-else translate-context="*/*/*">N/A</translate>
+                    <translate
+                      v-else
+                      translate-context="*/*/*"
+                    >
+                      N/A
+                    </translate>
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Content/*/*/Noun">Resolution date</translate>
+                    <translate translate-context="Content/*/*/Noun">
+                      Resolution date
+                    </translate>
                   </td>
                   <td>
-                    <human-date v-if="obj.handled_date" :date="obj.handled_date" :icon="true"></human-date>
-                    <translate v-else translate-context="*/*/*">N/A</translate>
+                    <human-date
+                      v-if="obj.handled_date"
+                      :date="obj.handled_date"
+                      :icon="true"
+                    />
+                    <translate
+                      v-else
+                      translate-context="*/*/*"
+                    >
+                      N/A
+                    </translate>
                   </td>
                 </tr>
                 <tr>
                   <td>
-                    <translate translate-context="Content/*/*/Noun">Internal notes</translate>
+                    <translate translate-context="Content/*/*/Noun">
+                      Internal notes
+                    </translate>
                   </td>
                   <td>
-                    <i class="comment icon"></i>
+                    <i class="comment icon" />
                     {{ obj.notes.length }}
                   </td>
                 </tr>
@@ -89,52 +138,85 @@
         </div>
       </div>
     </div>
-    <div class="main content" v-if="!isCollapsed">
+    <div
+      v-if="!isCollapsed"
+      class="main content"
+    >
       <div class="ui stackable two column grid">
         <div class="column">
           <h3>
-            <translate translate-context="*/*/Field.Label/Noun">Message</translate>
+            <translate translate-context="*/*/Field.Label/Noun">
+              Message
+            </translate>
           </h3>
           <p>
-            <translate translate-context="Content/Moderation/Paragraph">This user wants to sign-up on your pod.</translate>
+            <translate translate-context="Content/Moderation/Paragraph">
+              This user wants to sign-up on your pod.
+            </translate>
           </p>
           <template v-if="obj.metadata">
-            <div class="ui hidden divider"></div>
-            <div v-for="k in Object.keys(obj.metadata)" :key="k">
+            <div class="ui hidden divider" />
+            <div
+              v-for="k in Object.keys(obj.metadata)"
+              :key="k"
+            >
               <h4>{{ k }}</h4>
-              <p v-if="obj.metadata[k] && obj.metadata[k].length">{{ obj.metadata[k] }}</p>
-              <translate v-else translate-context="*/*/*">N/A</translate>
-              <div class="ui hidden divider"></div>
+              <p v-if="obj.metadata[k] && obj.metadata[k].length">
+                {{ obj.metadata[k] }}
+              </p>
+              <translate
+                v-else
+                translate-context="*/*/*"
+              >
+                N/A
+              </translate>
+              <div class="ui hidden divider" />
             </div>
           </template>
         </div>
         <aside class="column">
           <div v-if="obj.status != 'approved'">
             <h3>
-              <translate translate-context="Content/*/*/Noun">Actions</translate>
+              <translate translate-context="Content/*/*/Noun">
+                Actions
+              </translate>
             </h3>
             <div class="ui labelled icon basic buttons">
               <button
                 v-if="obj.status === 'pending' || obj.status === 'refused'"
+                :class="['ui', {loading: isLoading}, 'button']"
                 @click="approve(true)"
-                :class="['ui', {loading: isLoading}, 'button']">
-                <i class="success check icon"></i>&nbsp;
-                <translate translate-context="Content/*/Button.Label/Verb">Approve</translate>
+              >
+                <i class="success check icon" />&nbsp;
+                <translate translate-context="Content/*/Button.Label/Verb">
+                  Approve
+                </translate>
               </button>
               <button
                 v-if="obj.status === 'pending'"
+                :class="['ui', {loading: isLoading}, 'button']"
                 @click="approve(false)"
-                :class="['ui', {loading: isLoading}, 'button']">
-                <i class="danger x icon"></i>&nbsp;
-                <translate translate-context="Content/*/Button.Label">Refuse</translate>
+              >
+                <i class="danger x icon" />&nbsp;
+                <translate translate-context="Content/*/Button.Label">
+                  Refuse
+                </translate>
               </button>
             </div>
           </div>
           <h3>
-            <translate translate-context="Content/*/*/Noun">Internal notes</translate>
+            <translate translate-context="Content/*/*/Noun">
+              Internal notes
+            </translate>
           </h3>
-          <notes-thread @deleted="handleRemovedNote($event)" :notes="obj.notes" />
-          <note-form @created="obj.notes.push($event)" :target="{type: 'request', uuid: obj.uuid}" />
+          <notes-thread
+            :notes="obj.notes"
+            @deleted="handleRemovedNote($event)"
+          />
+          <note-form
+            :target="{type: 'request', uuid: obj.uuid}"
+            @created="obj.notes.push($event)"
+          />
         </aside>
       </div>
     </div>
@@ -145,48 +227,47 @@
 import axios from 'axios'
 import NoteForm from '@/components/manage/moderation/NoteForm'
 import NotesThread from '@/components/manage/moderation/NotesThread'
-import {setUpdate} from '@/utils'
 import showdown from 'showdown'
 
-
 export default {
-  props: {
-    obj: {required: true},
-  },
   components: {
     NoteForm,
-    NotesThread,
+    NotesThread
+  },
+  props: {
+    initObj: { type: Object, required: true }
   },
   data () {
     return {
       markdown: new showdown.Converter(),
       isLoading: false,
       isCollapsed: false,
+      obj: this.initObj
     }
   },
   methods: {
     approve (v) {
-      let url = `manage/moderation/requests/${this.obj.uuid}/`
-      let self = this
-      let newStatus = v ? 'approved' : 'refused'
+      const url = `manage/moderation/requests/${this.obj.uuid}/`
+      const self = this
+      const newStatus = v ? 'approved' : 'refused'
       this.isLoading = true
-      axios.patch(url, {status: newStatus}).then((response) => {
+      axios.patch(url, { status: newStatus }).then((response) => {
         self.$emit('handled', newStatus)
         self.isLoading = false
         self.obj.status = newStatus
         if (v) {
           self.isCollapsed = true
         }
-        self.$store.commit('ui/incrementNotifications', {count: -1, type: 'pendingReviewRequests'})
-      }, error => {
+        self.$store.commit('ui/incrementNotifications', { count: -1, type: 'pendingReviewRequests' })
+      }, () => {
         self.isLoading = false
       })
     },
     handleRemovedNote (uuid) {
       this.obj.notes = this.obj.notes.filter((note) => {
-        return note.uuid != uuid
+        return note.uuid !== uuid
       })
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/users/InvitationForm.vue b/front/src/components/manage/users/InvitationForm.vue
index f8a0e88e2..72011a6aa 100644
--- a/front/src/components/manage/users/InvitationForm.vue
+++ b/front/src/components/manage/users/InvitationForm.vue
@@ -1,41 +1,92 @@
 <template>
   <div>
-    <form class="ui form" @submit.prevent="submit">
-      <div v-if="errors.length > 0" role="alert" class="ui negative message">
-        <h4 class="header"><translate translate-context="Content/Admin/Error message.Title">Error while creating invitation</translate></h4>
+    <form
+      class="ui form"
+      @submit.prevent="submit"
+    >
+      <div
+        v-if="errors.length > 0"
+        role="alert"
+        class="ui negative message"
+      >
+        <h4 class="header">
+          <translate translate-context="Content/Admin/Error message.Title">
+            Error while creating invitation
+          </translate>
+        </h4>
         <ul class="list">
-          <li v-for="error in errors">{{ error }}</li>
+          <li
+            v-for="(error, key) in errors"
+            :key="key"
+          >
+            {{ error }}
+          </li>
         </ul>
       </div>
       <div class="inline fields">
         <div class="ui field">
           <label for="invitation-code"><translate translate-context="Content/*/Input.Label">Invitation code</translate></label>
-          <input for="invitation-code" name="code" type="text" v-model="code" :placeholder="labels.placeholder" />
+          <input
+            v-model="code"
+            for="invitation-code"
+            name="code"
+            type="text"
+            :placeholder="labels.placeholder"
+          >
         </div>
         <div class="ui field">
-          <button :class="['ui', {loading: isLoading}, 'button']" :disabled="isLoading" type="submit">
-            <translate translate-context="Content/Admin/Button.Label/Verb">Get a new invitation</translate>
+          <button
+            :class="['ui', {loading: isLoading}, 'button']"
+            :disabled="isLoading"
+            type="submit"
+          >
+            <translate translate-context="Content/Admin/Button.Label/Verb">
+              Get a new invitation
+            </translate>
           </button>
         </div>
       </div>
     </form>
     <div v-if="invitations.length > 0">
-      <div class="ui hidden divider"></div>
+      <div class="ui hidden divider" />
       <table class="ui ui basic table">
         <thead>
           <tr>
-            <th><translate translate-context="Content/Admin/Table.Label/Noun">Code</translate></th>
-            <th><translate translate-context="Content/Admin/Table.Label/Noun">Share link</translate></th>
+            <th>
+              <translate translate-context="Content/Admin/Table.Label/Noun">
+                Code
+              </translate>
+            </th>
+            <th>
+              <translate translate-context="Content/Admin/Table.Label/Noun">
+                Share link
+              </translate>
+            </th>
           </tr>
         </thead>
         <tbody>
-          <tr v-for="invitation in invitations" :key="invitation.code">
+          <tr
+            v-for="invitation in invitations"
+            :key="invitation.code"
+          >
             <td>{{ invitation.code.toUpperCase() }}</td>
-            <td><a :href="getUrl(invitation.code)" target="_blank">{{ getUrl(invitation.code) }}</a></td>
+            <td>
+              <a
+                :href="getUrl(invitation.code)"
+                target="_blank"
+              >{{ getUrl(invitation.code) }}</a>
+            </td>
           </tr>
         </tbody>
       </table>
-      <button class="ui basic button" @click="invitations = []"><translate translate-context="Content/Library/Button.Label">Clear</translate></button>
+      <button
+        class="ui basic button"
+        @click="invitations = []"
+      >
+        <translate translate-context="Content/Library/Button.Label">
+          Clear
+        </translate>
+      </button>
     </div>
   </div>
 </template>
@@ -61,11 +112,11 @@ export default {
   },
   methods: {
     submit () {
-      let self = this
+      const self = this
       this.isLoading = true
       this.errors = []
-      let url = 'manage/users/invitations/'
-      let payload = {
+      const url = 'manage/users/invitations/'
+      const payload = {
         code: this.code
       }
       axios.post(url, payload).then((response) => {
@@ -77,7 +128,7 @@ export default {
       })
     },
     getUrl (code) {
-      return this.$store.getters['instance/absoluteUrl'](this.$router.resolve({name: 'signup', query: {invitation: code.toUpperCase()}}).href)
+      return this.$store.getters['instance/absoluteUrl'](this.$router.resolve({ name: 'signup', query: { invitation: code.toUpperCase() } }).href)
     }
   }
 }
diff --git a/front/src/components/manage/users/InvitationsTable.vue b/front/src/components/manage/users/InvitationsTable.vue
index 62e5a4f05..289af6398 100644
--- a/front/src/components/manage/users/InvitationsTable.vue
+++ b/front/src/components/manage/users/InvitationsTable.vue
@@ -4,58 +4,126 @@
       <div class="fields">
         <div class="ui field">
           <label for="invitations-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
-          <input id="invitations-search" name="search" type="text" v-model="search" :placeholder="labels.searchPlaceholder" />
+          <input
+            id="invitations-search"
+            v-model="search"
+            name="search"
+            type="text"
+            :placeholder="labels.searchPlaceholder"
+          >
         </div>
         <div class="field">
           <label for="invitations-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-          <select id="invitations-ordering" class="ui dropdown" v-model="ordering">
-            <option v-for="option in orderingOptions" :value="option[0]">
+          <select
+            id="invitations-ordering"
+            v-model="ordering"
+            class="ui dropdown"
+          >
+            <option
+              v-for="(option, key) in orderingOptions"
+              :key="key"
+              :value="option[0]"
+            >
               {{ sharedLabels.filters[option[1]] }}
             </option>
           </select>
         </div>
         <div class="field">
           <label for="invitations-status"><translate translate-context="*/*/*">Status</translate></label>
-          <select id="invitations-status" class="ui dropdown" v-model="isOpen">
-            <option :value="null"><translate translate-context="Content/*/Dropdown">All</translate></option>
-            <option :value="true"><translate translate-context="Content/Admin/Dropdown/Adjective">Open</translate></option>
-            <option :value="false"><translate translate-context="Content/Admin/Dropdown/Adjective">Expired/used</translate></option>
+          <select
+            id="invitations-status"
+            v-model="isOpen"
+            class="ui dropdown"
+          >
+            <option :value="null">
+              <translate translate-context="Content/*/Dropdown">
+                All
+              </translate>
+            </option>
+            <option :value="true">
+              <translate translate-context="Content/Admin/Dropdown/Adjective">
+                Open
+              </translate>
+            </option>
+            <option :value="false">
+              <translate translate-context="Content/Admin/Dropdown/Adjective">
+                Expired/used
+              </translate>
+            </option>
           </select>
         </div>
       </div>
-      </div>
+    </div>
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-          <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <action-table
         v-if="result"
-        @action-launched="fetchData"
         :objects-data="result"
         :actions="actions"
         :action-url="'manage/users/invitations/action/'"
-        :filters="actionFilters">
+        :filters="actionFilters"
+        @action-launched="fetchData"
+      >
         <template slot="header-cells">
-          <th><translate translate-context="*/*/*">Owner</translate></th>
-          <th><translate translate-context="*/*/*">Status</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Creation date</translate></th>
-          <th><translate translate-context="Content/Admin/Table.Label/Noun">Expiration date</translate></th>
-          <th><translate translate-context="Content/Admin/Table.Label/Noun">Code</translate></th>
+          <th>
+            <translate translate-context="*/*/*">
+              Owner
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Status
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Creation date
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Admin/Table.Label/Noun">
+              Expiration date
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Admin/Table.Label/Noun">
+              Code
+            </translate>
+          </th>
         </template>
-        <template slot="row-cells" slot-scope="scope">
+        <template
+          slot="row-cells"
+          slot-scope="scope"
+        >
           <td>
-            <router-link :to="{name: 'manage.users.users.detail', params: {id: scope.obj.id }}">{{ scope.obj.owner.username }}</router-link>
+            <router-link :to="{name: 'manage.users.users.detail', params: {id: scope.obj.id }}">
+              {{ scope.obj.owner.username }}
+            </router-link>
           </td>
           <td>
-            <span v-if="scope.obj.users.length > 0" class="ui success basic label"><translate translate-context="Content/Admin/Table">Used</translate></span>
-            <span v-else-if="moment().isAfter(scope.obj.expiration_date)" class="ui danger basic label"><translate translate-context="Content/Admin/Table">Expired</translate></span>
-            <span v-else class="ui basic label"><translate translate-context="Content/Admin/Table">Not used</translate></span>
+            <span
+              v-if="scope.obj.users.length > 0"
+              class="ui success basic label"
+            ><translate translate-context="Content/Admin/Table">Used</translate></span>
+            <span
+              v-else-if="moment().isAfter(scope.obj.expiration_date)"
+              class="ui danger basic label"
+            ><translate translate-context="Content/Admin/Table">Expired</translate></span>
+            <span
+              v-else
+              class="ui basic label"
+            ><translate translate-context="Content/Admin/Table">Not used</translate></span>
           </td>
           <td>
-            <human-date :date="scope.obj.creation_date"></human-date>
+            <human-date :date="scope.obj.creation_date" />
           </td>
           <td>
-            <human-date :date="scope.obj.expiration_date"></human-date>
+            <human-date :date="scope.obj.expiration_date" />
           </td>
           <td>
             {{ scope.obj.code.toUpperCase() }}
@@ -66,18 +134,20 @@
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="selectPage"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-        ></pagination>
+        @page-changed="selectPage"
+      />
 
       <span v-if="result && result.results.length > 0">
-        <translate translate-context="Content/*/Paragraph"
+        <translate
+          translate-context="Content/*/Paragraph"
           translate-plural="Showing results %{ start } to %{ end } from %{ total }"
           :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
-          :translate-n="result.count">
+          :translate-n="result.count"
+        >
           Showing one result
         </translate>
       </span>
@@ -95,16 +165,16 @@ import OrderingMixin from '@/components/mixins/Ordering'
 import TranslationsMixin from '@/components/mixins/Translations'
 
 export default {
-  mixins: [OrderingMixin, TranslationsMixin],
-  props: {
-    filters: {type: Object, required: false}
-  },
   components: {
     Pagination,
     ActionTable
   },
+  mixins: [OrderingMixin, TranslationsMixin],
+  props: {
+    filters: { type: Object, required: false, default: function () { return {} } }
+  },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
     return {
       moment,
       isLoading: false,
@@ -122,33 +192,6 @@ export default {
 
     }
   },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search,
-        'is_open': this.isOpen,
-        'ordering': this.getOrderingAsString()
-      }, this.filters)
-      let self = this
-      self.isLoading = true
-      self.checked = []
-      axios.get('/manage/users/invitations/', {params: params}).then((response) => {
-        self.result = response.data
-        self.isLoading = false
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    },
-    selectPage: function (page) {
-      this.page = page
-    }
-  },
   computed: {
     labels () {
       return {
@@ -156,7 +199,7 @@ export default {
       }
     },
     actionFilters () {
-      var currentFilters = {
+      const currentFilters = {
         q: this.search
       }
       if (this.filters) {
@@ -166,7 +209,7 @@ export default {
       }
     },
     actions () {
-      let deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
+      const deleteLabel = this.$pgettext('*/*/*/Verb', 'Delete')
       return [
         {
           name: 'delete',
@@ -198,6 +241,33 @@ export default {
       this.page = 1
       this.fetchData()
     }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search,
+        is_open: this.isOpen,
+        ordering: this.getOrderingAsString()
+      }, this.filters)
+      const self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/users/invitations/', { params: params }).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    }
   }
 }
 </script>
diff --git a/front/src/components/manage/users/UsersTable.vue b/front/src/components/manage/users/UsersTable.vue
index 3ad6210ca..ed1f1491b 100644
--- a/front/src/components/manage/users/UsersTable.vue
+++ b/front/src/components/manage/users/UsersTable.vue
@@ -4,73 +4,172 @@
       <div class="fields">
         <div class="ui field">
           <label for="users-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
-          <input id="users-search" name="search" type="text" v-model="search" :placeholder="labels.searchPlaceholder" />
+          <input
+            id="users-search"
+            v-model="search"
+            name="search"
+            type="text"
+            :placeholder="labels.searchPlaceholder"
+          >
         </div>
         <div class="field">
           <label for="users-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-          <select id="users-ordering" class="ui dropdown" v-model="ordering">
-            <option v-for="option in orderingOptions" :value="option[0]">
+          <select
+            id="users-ordering"
+            v-model="ordering"
+            class="ui dropdown"
+          >
+            <option
+              v-for="(option, key) in orderingOptions"
+              :key="key"
+              :value="option[0]"
+            >
               {{ sharedLabels.filters[option[1]] }}
             </option>
           </select>
         </div>
         <div class="field">
           <label for="users-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label>
-          <select id="users-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-            <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-            <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+          <select
+            id="users-ordering-direction"
+            v-model="orderingDirection"
+            class="ui dropdown"
+          >
+            <option value="+">
+              <translate translate-context="Content/Search/Dropdown">
+                Ascending
+              </translate>
+            </option>
+            <option value="-">
+              <translate translate-context="Content/Search/Dropdown">
+                Descending
+              </translate>
+            </option>
           </select>
         </div>
       </div>
-      </div>
+    </div>
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-          <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <action-table
         v-if="result"
-        @action-launched="fetchData"
         :objects-data="result"
         :actions="actions"
         :action-url="'manage/library/uploads/action/'"
-        :filters="actionFilters">
+        :filters="actionFilters"
+        @action-launched="fetchData"
+      >
         <template slot="header-cells">
-          <th><translate translate-context="Content/*/*">Username</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Email</translate></th>
-          <th><translate translate-context="Content/Admin/Table.Label/Short, Noun">Account status</translate></th>
-          <th><translate translate-context="Content/Admin/Table.Label/Short, Noun (Value is a date)">Sign-up</translate></th>
-          <th><translate translate-context="Content/Profile/Table.Label/Short, Noun (Value is a date)">Last activity</translate></th>
-          <th><translate translate-context="Content/*/*/Noun">Permissions</translate></th>
-          <th><translate translate-context="*/*/*">Status</translate></th>
+          <th>
+            <translate translate-context="Content/*/*">
+              Username
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Email
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Admin/Table.Label/Short, Noun">
+              Account status
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Admin/Table.Label/Short, Noun (Value is a date)">
+              Sign-up
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Profile/Table.Label/Short, Noun (Value is a date)">
+              Last activity
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/*/*/Noun">
+              Permissions
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Status
+            </translate>
+          </th>
         </template>
-        <template slot="row-cells" slot-scope="scope">
+        <template
+          slot="row-cells"
+          slot-scope="scope"
+        >
           <td>
-            <router-link v-if="scope.obj.actor" :to="{name: 'manage.moderation.accounts.detail', params: {id: scope.obj.actor.full_username }}">{{ scope.obj.username }}</router-link>
-            <router-link v-else :to="{name: 'manage.moderation.accounts.detail', params: {id: scope.obj.full_username }}">{{ scope.obj.username }}</router-link>
+            <router-link
+              v-if="scope.obj.actor"
+              :to="{name: 'manage.moderation.accounts.detail', params: {id: scope.obj.actor.full_username }}"
+            >
+              {{ scope.obj.username }}
+            </router-link>
+            <router-link
+              v-else
+              :to="{name: 'manage.moderation.accounts.detail', params: {id: scope.obj.full_username }}"
+            >
+              {{ scope.obj.username }}
+            </router-link>
           </td>
           <td>
             <span>{{ scope.obj.email }}</span>
           </td>
           <td>
-            <span v-if="scope.obj.is_active" class="ui basic success label"><translate translate-context="Content/Admin/Table">Active</translate></span>
-            <span v-else class="ui basic label"><translate translate-context="Content/Admin/Table">Inactive</translate></span>
+            <span
+              v-if="scope.obj.is_active"
+              class="ui basic success label"
+            ><translate translate-context="Content/Admin/Table">Active</translate></span>
+            <span
+              v-else
+              class="ui basic label"
+            ><translate translate-context="Content/Admin/Table">Inactive</translate></span>
           </td>
           <td>
-            <human-date :date="scope.obj.date_joined"></human-date>
+            <human-date :date="scope.obj.date_joined" />
           </td>
           <td>
-            <human-date v-if="scope.obj.last_activity" :date="scope.obj.last_activity"></human-date>
-            <template v-else><translate translate-context="*/*/*">N/A</translate></template>
+            <human-date
+              v-if="scope.obj.last_activity"
+              :date="scope.obj.last_activity"
+            />
+            <template v-else>
+              <translate translate-context="*/*/*">
+                N/A
+              </translate>
+            </template>
           </td>
           <td>
-            <template v-for="p in permissions">
-              <span class="ui basic tiny label" v-if="scope.obj.permissions[p.code]">{{ p.label }}</span>
+            <template
+              v-for="(p, key) in permissions"
+            >
+              <span
+                v-if="scope.obj.permissions[p.code]"
+                :key="key"
+                class="ui basic tiny label"
+              >{{ p.label }}</span>
             </template>
           </td>
           <td>
-            <span v-if="scope.obj.is_superuser" class="ui pink label"><translate translate-context="Content/Admin/Table.User role">Admin</translate></span>
-            <span v-else-if="scope.obj.is_staff" class="ui purple label"><translate translate-context="Content/Profile/User role">Staff member</translate></span>
-            <span v-else class="ui basic label"><translate translate-context="Content/Admin/Table, User role">Regular user</translate></span>
+            <span
+              v-if="scope.obj.is_superuser"
+              class="ui pink label"
+            ><translate translate-context="Content/Admin/Table.User role">Admin</translate></span>
+            <span
+              v-else-if="scope.obj.is_staff"
+              class="ui purple label"
+            ><translate translate-context="Content/Profile/User role">Staff member</translate></span>
+            <span
+              v-else
+              class="ui basic label"
+            ><translate translate-context="Content/Admin/Table, User role">Regular user</translate></span>
           </td>
         </template>
       </action-table>
@@ -78,18 +177,20 @@
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="selectPage"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-        ></pagination>
+        @page-changed="selectPage"
+      />
 
       <span v-if="result && result.results.length > 0">
-        <translate translate-context="Content/*/Paragraph"
+        <translate
+          translate-context="Content/*/Paragraph"
           translate-plural="Showing results %{ start } to %{ end } from %{ total }"
           :translate-params="{start: ((page-1) * paginateBy) + 1, end: ((page-1) * paginateBy) + result.results.length, total: result.count}"
-          :translate-n="result.count">
+          :translate-n="result.count"
+        >
           Showing one result
         </translate>
       </span>
@@ -107,16 +208,16 @@ import OrderingMixin from '@/components/mixins/Ordering'
 import TranslationsMixin from '@/components/mixins/Translations'
 
 export default {
-  mixins: [OrderingMixin, TranslationsMixin],
-  props: {
-    filters: {type: Object, required: false}
-  },
   components: {
     Pagination,
     ActionTable
   },
+  mixins: [OrderingMixin, TranslationsMixin],
+  props: {
+    filters: { type: Object, required: false, default: function () { return {} } }
+  },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-date_joined')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-date_joined')
     return {
       time,
       isLoading: false,
@@ -134,32 +235,6 @@ export default {
 
     }
   },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search,
-        'ordering': this.getOrderingAsString()
-      }, this.filters)
-      let self = this
-      self.isLoading = true
-      self.checked = []
-      axios.get('/manage/users/users/', {params: params}).then((response) => {
-        self.result = response.data
-        self.isLoading = false
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    },
-    selectPage: function (page) {
-      this.page = page
-    }
-  },
   computed: {
     labels () {
       return {
@@ -172,21 +247,21 @@ export default {
     permissions () {
       return [
         {
-          'code': 'library',
-          'label': this.$pgettext('*/*/*/Noun', 'Library')
+          code: 'library',
+          label: this.$pgettext('*/*/*/Noun', 'Library')
         },
         {
-          'code': 'moderation',
-          'label': this.$pgettext('*/Moderation/*', 'Moderation')
+          code: 'moderation',
+          label: this.$pgettext('*/Moderation/*', 'Moderation')
         },
         {
-          'code': 'settings',
-          'label': this.$pgettext('*/*/*/Noun', 'Settings')
+          code: 'settings',
+          label: this.$pgettext('*/*/*/Noun', 'Settings')
         }
       ]
     },
     actionFilters () {
-      var currentFilters = {
+      const currentFilters = {
         q: this.search
       }
       if (this.filters) {
@@ -219,6 +294,32 @@ export default {
     orderingDirection () {
       this.fetchData()
     }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search,
+        ordering: this.getOrderingAsString()
+      }, this.filters)
+      const self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/manage/users/users/', { params: params }).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    }
   }
 }
 </script>
diff --git a/front/src/components/mixins/Ordering.vue b/front/src/components/mixins/Ordering.vue
index d91f28db5..787519e91 100644
--- a/front/src/components/mixins/Ordering.vue
+++ b/front/src/components/mixins/Ordering.vue
@@ -1,50 +1,50 @@
 <script>
 export default {
   props: {
-    defaultOrdering: {type: String, required: false},
-    orderingConfigName: {type: String, required: false},
+    defaultOrdering: { type: String, required: false, default: '' },
+    orderingConfigName: { type: String, required: false, default: '' }
   },
   computed: {
     orderingConfig () {
       return this.$store.state.ui.routePreferences[this.orderingConfigName || this.$route.name]
     },
     paginateBy: {
-      set(paginateBy) {
+      set (paginateBy) {
         this.$store.commit('ui/paginateBy', {
           route: this.$route.name,
           value: paginateBy
         })
       },
-      get() {
+      get () {
         return this.orderingConfig.paginateBy
       }
     },
     ordering: {
-      set(ordering) {
+      set (ordering) {
         this.$store.commit('ui/ordering', {
           route: this.$route.name,
           value: ordering
         })
       },
-      get() {
+      get () {
         return this.orderingConfig.ordering
       }
     },
     orderingDirection: {
-      set(orderingDirection) {
+      set (orderingDirection) {
         this.$store.commit('ui/orderingDirection', {
           route: this.$route.name,
           value: orderingDirection
         })
       },
-      get() {
+      get () {
         return this.orderingConfig.orderingDirection
       }
-    },
+    }
   },
   methods: {
     getOrderingFromString (s) {
-      let parts = s.split('-')
+      const parts = s.split('-')
       if (parts.length > 1) {
         return {
           direction: '-',
diff --git a/front/src/components/mixins/Pagination.vue b/front/src/components/mixins/Pagination.vue
index 532faaaa3..1bc7a05b9 100644
--- a/front/src/components/mixins/Pagination.vue
+++ b/front/src/components/mixins/Pagination.vue
@@ -1,8 +1,8 @@
 <script>
 export default {
   props: {
-    defaultPage: {required: false, default: 1},
-    defaultPaginateBy: {required: false}
+    defaultPage: { type: Number, required: false, default: 1 },
+    defaultPaginateBy: { type: Number, required: false, default: 1 }
   }
 }
 </script>
diff --git a/front/src/components/mixins/PlayOptions.vue b/front/src/components/mixins/PlayOptions.vue
index 2a9ed4345..ebe1810ef 100644
--- a/front/src/components/mixins/PlayOptions.vue
+++ b/front/src/components/mixins/PlayOptions.vue
@@ -1,9 +1,10 @@
 <script>
 import axios from 'axios'
+import jQuery from 'jquery'
 
 export default {
   computed: {
-      playable () {
+    playable () {
       if (this.isPlayable) {
         return true
       }
@@ -34,27 +35,28 @@ export default {
       if (this.artist) {
         return this.artist
       }
-    },
+      return null
+    }
   },
   methods: {
     filterArtist () {
-      this.$store.dispatch('moderation/hide', {type: 'artist', target: this.filterableArtist})
+      this.$store.dispatch('moderation/hide', { type: 'artist', target: this.filterableArtist })
     },
-    activateTrack(track, index) {
+    activateTrack (track, index) {
       if (
         this.currentTrack &&
         this.isPlaying &&
         track.id === this.currentTrack.id
       ) {
-        this.pausePlayback();
+        this.pausePlayback()
       } else if (
         this.currentTrack &&
         !this.isPlaying &&
         track.id === this.currentTrack.id
       ) {
-        this.resumePlayback();
+        this.resumePlayback()
       } else {
-        this.replacePlay(this.tracks, index);
+        this.replacePlay(this.tracks, index)
       }
     },
     getTracksPage (page, params, resolve, tracks) {
@@ -64,13 +66,13 @@ export default {
       }
       // when fetching artists/or album tracks, sometimes, we may have to fetch
       // multiple pages
-      let self = this
-      params['page_size'] = 100
-      params['page'] = page
-      params['hidden'] = ''
-      params['playable'] = 'true'
+      const self = this
+      params.page_size = 100
+      params.page = page
+      params.hidden = ''
+      params.playable = 'true'
       tracks = tracks || []
-      axios.get('tracks/', {params: params}).then((response) => {
+      axios.get('tracks/', { params: params }).then((response) => {
         response.data.results.forEach(t => {
           tracks.push(t)
         })
@@ -82,9 +84,9 @@ export default {
       })
     },
     getPlayableTracks () {
-      let self = this
+      const self = this
       this.isLoading = true
-      let getTracks = new Promise((resolve, reject) => {
+      const getTracks = new Promise((resolve, reject) => {
         if (self.tracks) {
           resolve(self.tracks)
         } else if (self.track) {
@@ -97,9 +99,9 @@ export default {
             resolve([self.track])
           }
         } else if (self.playlist) {
-          let url = 'playlists/' + self.playlist.id + '/'
+          const url = 'playlists/' + self.playlist.id + '/'
           axios.get(url + 'tracks/').then((response) => {
-            let artistIds = self.$store.getters['moderation/artistFilters']().map((f) => {
+            const artistIds = self.$store.getters['moderation/artistFilters']().map((f) => {
               return f.target.id
             })
             let tracks = response.data.results.map(plt => {
@@ -108,21 +110,21 @@ export default {
             if (artistIds.length > 0) {
               // skip tracks from hidden artists
               tracks = tracks.filter((t) => {
-                let matchArtist = artistIds.indexOf(t.artist.id) > -1
-                return !(matchArtist || t.album && artistIds.indexOf(t.album.artist.id) > -1)
+                const matchArtist = artistIds.indexOf(t.artist.id) > -1
+                return !((matchArtist || t.album) && artistIds.indexOf(t.album.artist.id) > -1)
               })
             }
 
             resolve(tracks)
           })
         } else if (self.artist) {
-          let params = {'artist': self.artist.id, include_channels: 'true', 'ordering': 'album__release_date,disc_number,position'}
+          const params = { artist: self.artist.id, include_channels: 'true', ordering: 'album__release_date,disc_number,position' }
           self.getTracksPage(1, params, resolve)
         } else if (self.album) {
-          let params = {'album': self.album.id, include_channels: 'true', 'ordering': 'disc_number,position'}
+          const params = { album: self.album.id, include_channels: 'true', ordering: 'disc_number,position' }
           self.getTracksPage(1, params, resolve)
         } else if (self.library) {
-          let params = {'library': self.library.uuid, 'ordering': '-creation_date'}
+          const params = { library: self.library.uuid, ordering: '-creation_date' }
           self.getTracksPage(1, params, resolve)
         }
       })
@@ -136,17 +138,17 @@ export default {
       })
     },
     add () {
-      let self = this
+      const self = this
       this.getPlayableTracks().then((tracks) => {
-        self.$store.dispatch('queue/appendMany', {tracks: tracks}).then(() => self.addMessage(tracks))
+        self.$store.dispatch('queue/appendMany', { tracks: tracks }).then(() => self.addMessage(tracks))
       })
       jQuery(self.$el).find('.ui.dropdown').dropdown('hide')
     },
     replacePlay () {
-      let self = this
+      const self = this
       self.$store.dispatch('queue/clean')
       this.getPlayableTracks().then((tracks) => {
-        self.$store.dispatch('queue/appendMany', {tracks: tracks}).then(() => {
+        self.$store.dispatch('queue/appendMany', { tracks: tracks }).then(() => {
           if (self.track) {
             // set queue position to selected track
             const trackIndex = self.tracks.findIndex(track => track.id === self.track.id)
@@ -158,11 +160,11 @@ export default {
       jQuery(self.$el).find('.ui.dropdown').dropdown('hide')
     },
     addNext (next) {
-      let self = this
-      let wasEmpty = this.$store.state.queue.tracks.length === 0
+      const self = this
+      const wasEmpty = this.$store.state.queue.tracks.length === 0
       this.getPlayableTracks().then((tracks) => {
-        self.$store.dispatch('queue/appendMany', {tracks: tracks, index: self.$store.state.queue.currentIndex + 1}).then(() => self.addMessage(tracks))
-        let goNext = next && !wasEmpty
+        self.$store.dispatch('queue/appendMany', { tracks: tracks, index: self.$store.state.queue.currentIndex + 1 }).then(() => self.addMessage(tracks))
+        const goNext = next && !wasEmpty
         if (goNext) {
           self.$store.dispatch('queue/next')
         }
@@ -173,12 +175,12 @@ export default {
       if (tracks.length < 1) {
         return
       }
-      let msg = this.$npgettext('*/Queue/Message', '%{ count } track was added to your queue', '%{ count } tracks were added to your queue', tracks.length)
+      const msg = this.$npgettext('*/Queue/Message', '%{ count } track was added to your queue', '%{ count } tracks were added to your queue', tracks.length)
       this.$store.commit('ui/addMessage', {
-        content: this.$gettextInterpolate(msg, {count: tracks.length}),
+        content: this.$gettextInterpolate(msg, { count: tracks.length }),
         date: new Date()
       })
-    },
+    }
   }
 }
-</script>
\ No newline at end of file
+</script>
diff --git a/front/src/components/mixins/Report.vue b/front/src/components/mixins/Report.vue
index 403b89f24..66a49540b 100644
--- a/front/src/components/mixins/Report.vue
+++ b/front/src/components/mixins/Report.vue
@@ -1,18 +1,18 @@
 <script>
 export default {
   methods: {
-    getReportableObjs ({track, album, artist, playlist, account, library, channel}) {
-      let reportableObjs = []
+    getReportableObjs ({ track, album, artist, playlist, account, library, channel }) {
+      const reportableObjs = []
       if (account) {
-        let accountLabel = this.$pgettext('*/Moderation/*/Verb', "Report @%{ username }…")
+        const accountLabel = this.$pgettext('*/Moderation/*/Verb', 'Report @%{ username }…')
         reportableObjs.push({
-          label: this.$gettextInterpolate(accountLabel, {username: account.preferred_username}),
+          label: this.$gettextInterpolate(accountLabel, { username: account.preferred_username }),
           target: {
             type: 'account',
             _obj: account,
             full_username: account.full_username,
             label: account.full_username,
-            typeLabel: this.$pgettext("*/*/*/Noun", 'Account'),
+            typeLabel: this.$pgettext('*/*/*/Noun', 'Account')
           }
         })
         if (track) {
@@ -22,13 +22,13 @@ export default {
       }
       if (track) {
         reportableObjs.push({
-          label: this.$pgettext('*/Moderation/*/Verb', "Report this track…"),
+          label: this.$pgettext('*/Moderation/*/Verb', 'Report this track…'),
           target: {
             type: 'track',
             id: track.id,
             _obj: track,
             label: track.title,
-            typeLabel: this.$pgettext("*/*/*/Noun", 'Track'),
+            typeLabel: this.$pgettext('*/*/*/Noun', 'Track')
           }
         })
         album = track.album
@@ -36,13 +36,13 @@ export default {
       }
       if (album) {
         reportableObjs.push({
-          label: this.$pgettext('*/Moderation/*/Verb', "Report this album…"),
+          label: this.$pgettext('*/Moderation/*/Verb', 'Report this album…'),
           target: {
             type: 'album',
             id: album.id,
             label: album.title,
             _obj: album,
-            typeLabel: this.$pgettext("*/*/*", 'Album'),
+            typeLabel: this.$pgettext('*/*/*', 'Album')
           }
         })
         if (!artist) {
@@ -52,54 +52,53 @@ export default {
 
       if (channel) {
         reportableObjs.push({
-          label: this.$pgettext('*/Moderation/*/Verb', "Report this channel…"),
+          label: this.$pgettext('*/Moderation/*/Verb', 'Report this channel…'),
           target: {
             type: 'channel',
             uuid: channel.uuid,
             label: channel.artist.name,
             _obj: channel,
-            typeLabel: this.$pgettext("*/*/*", 'Channel'),
+            typeLabel: this.$pgettext('*/*/*', 'Channel')
           }
         })
-      }
-      else if (artist) {
+      } else if (artist) {
         reportableObjs.push({
-          label: this.$pgettext('*/Moderation/*/Verb', "Report this artist…"),
+          label: this.$pgettext('*/Moderation/*/Verb', 'Report this artist…'),
           target: {
             type: 'artist',
             id: artist.id,
             label: artist.name,
             _obj: artist,
-            typeLabel: this.$pgettext("*/*/*/Noun", 'Artist'),
+            typeLabel: this.$pgettext('*/*/*/Noun', 'Artist')
           }
         })
       }
       if (playlist) {
         reportableObjs.push({
-          label: this.$pgettext('*/Moderation/*/Verb', "Report this playlist…"),
+          label: this.$pgettext('*/Moderation/*/Verb', 'Report this playlist…'),
           target: {
             type: 'playlist',
             id: playlist.id,
             label: playlist.name,
             _obj: playlist,
-            typeLabel: this.$pgettext("*/*/*", 'Playlist'),
+            typeLabel: this.$pgettext('*/*/*', 'Playlist')
           }
         })
       }
       if (library) {
         reportableObjs.push({
-          label: this.$pgettext('*/Moderation/*/Verb', "Report this library…"),
+          label: this.$pgettext('*/Moderation/*/Verb', 'Report this library…'),
           target: {
             type: 'library',
             uuid: library.uuid,
             label: library.name,
             _obj: library,
-            typeLabel: this.$pgettext("*/*/*/Noun", 'Library'),
+            typeLabel: this.$pgettext('*/*/*/Noun', 'Library')
           }
         })
       }
       return reportableObjs
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/mixins/SmartSearch.vue b/front/src/components/mixins/SmartSearch.vue
index ef4506039..f662f8a30 100644
--- a/front/src/components/mixins/SmartSearch.vue
+++ b/front/src/components/mixins/SmartSearch.vue
@@ -1,15 +1,39 @@
 <script>
 
-import {normalizeQuery, parseTokens, compileTokens} from '@/search'
+import { normalizeQuery, parseTokens, compileTokens } from '@/search'
 
 export default {
   props: {
-    defaultQuery: {type: String, required: false},
-    updateUrl: {type: Boolean, required: false, default: false},
+    defaultQuery: { type: String, required: false, default: '' },
+    updateUrl: { type: Boolean, required: false, default: false }
+  },
+  watch: {
+    'search.query' (newValue) {
+      this.search.tokens = parseTokens(normalizeQuery(newValue))
+    },
+    'search.tokens': {
+      handler (newValue) {
+        const newQuery = compileTokens(newValue)
+        if (this.updateUrl) {
+          const params = {}
+          if (newQuery) {
+            params.q = newQuery
+          }
+          this.$router.replace({
+            query: params
+          })
+        } else {
+          this.search.query = newQuery
+          this.page = 1
+          this.fetchData()
+        }
+      },
+      deep: true
+    }
   },
   methods: {
     getTokenValue (key, fallback) {
-      let matching = this.search.tokens.filter(t => {
+      const matching = this.search.tokens.filter(t => {
         return t.field === key
       })
       if (matching.length > 0) {
@@ -22,10 +46,10 @@ export default {
       if (!value) {
         // we remove existing matching tokens, if any
         this.search.tokens = this.search.tokens.filter(t => {
-          return t.field != key
+          return t.field !== key
         })
       } else {
-        let existing = this.search.tokens.filter(t => {
+        const existing = this.search.tokens.filter(t => {
           return t.field === key
         })
         if (existing.length > 0) {
@@ -35,34 +59,10 @@ export default {
           })
         } else {
           // we add a new token
-          this.search.tokens.push({field: key, value})
+          this.search.tokens.push({ field: key, value })
         }
       }
-    },
-  },
-  watch: {
-    'search.query' (newValue) {
-      this.search.tokens = parseTokens(normalizeQuery(newValue))
-    },
-    'search.tokens': {
-      handler (newValue) {
-        let newQuery = compileTokens(newValue)
-        if (this.updateUrl) {
-          let params = {}
-          if (newQuery) {
-            params.q = newQuery
-          }
-          this.$router.replace({
-            query: params
-          })
-        } else {
-          this.search.query = newQuery
-          this.page = 1
-          this.fetchData()
-        }
-      },
-      deep: true
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/components/mixins/Translations.vue b/front/src/components/mixins/Translations.vue
index 2d2631e87..4380ae6ee 100644
--- a/front/src/components/mixins/Translations.vue
+++ b/front/src/components/mixins/Translations.vue
@@ -10,12 +10,12 @@ export default {
             choices: {
               me: this.$pgettext('Content/Settings/Dropdown', 'Nobody except me'),
               instance: this.$pgettext('Content/Settings/Dropdown', 'Everyone on this instance'),
-              everyone: this.$pgettext('Content/Settings/Dropdown', 'Everyone, across all instances'),
+              everyone: this.$pgettext('Content/Settings/Dropdown', 'Everyone, across all instances')
             },
             shortChoices: {
               me: this.$pgettext('Content/Settings/Dropdown/Short', 'Private'),
               instance: this.$pgettext('Content/Settings/Dropdown/Short', 'Instance'),
-              everyone: this.$pgettext('Content/Settings/Dropdown/Short', 'Everyone'),
+              everyone: this.$pgettext('Content/Settings/Dropdown/Short', 'Everyone')
             }
           },
           import_status: {
@@ -23,46 +23,46 @@ export default {
             choices: {
               skipped: {
                 label: this.$pgettext('Content/Library/*', 'Skipped'),
-                help: this.$pgettext('Content/Library/Help text', 'This track is already present in one of your libraries'),
+                help: this.$pgettext('Content/Library/Help text', 'This track is already present in one of your libraries')
               },
               draft: {
                 label: this.$pgettext('Content/Library/*/Short', 'Draft'),
-                help: this.$pgettext('Content/Library/Help text', 'This track has been uploaded, but hasn\'t been scheduled for processing yet'),
+                help: this.$pgettext('Content/Library/Help text', 'This track has been uploaded, but hasn\'t been scheduled for processing yet')
               },
               pending: {
                 label: this.$pgettext('Content/Library/*/Short', 'Pending'),
-                help: this.$pgettext('Content/Library/Help text', 'This track has been uploaded, but hasn\'t been processed by the server yet'),
+                help: this.$pgettext('Content/Library/Help text', 'This track has been uploaded, but hasn\'t been processed by the server yet')
               },
               errored: {
                 label: this.$pgettext('Content/Library/Table/Short', 'Errored'),
-                help: this.$pgettext('Content/Library/Help text', 'This track could not be processed, please make sure it is tagged correctly'),
+                help: this.$pgettext('Content/Library/Help text', 'This track could not be processed, please make sure it is tagged correctly')
               },
               finished: {
                 label: this.$pgettext('Content/Library/*', 'Finished'),
-                help: this.$pgettext('Content/Library/Help text', 'Imported'),
-              },
+                help: this.$pgettext('Content/Library/Help text', 'Imported')
+              }
             }
           },
           report_type: {
             label: this.$pgettext('*/*/*', 'Category'),
             choices: {
-              takedown_request: this.$pgettext("Content/Moderation/Dropdown", "Takedown request"),
-              invalid_metadata: this.$pgettext("Popup/Import/Error.Label", "Invalid metadata"),
-              illegal_content: this.$pgettext("Content/Moderation/Dropdown", "Illegal content"),
-              offensive_content: this.$pgettext("Content/Moderation/Dropdown", "Offensive content"),
-              other: this.$pgettext("Content/Moderation/Dropdown", "Other"),
-            },
+              takedown_request: this.$pgettext('Content/Moderation/Dropdown', 'Takedown request'),
+              invalid_metadata: this.$pgettext('Popup/Import/Error.Label', 'Invalid metadata'),
+              illegal_content: this.$pgettext('Content/Moderation/Dropdown', 'Illegal content'),
+              offensive_content: this.$pgettext('Content/Moderation/Dropdown', 'Offensive content'),
+              other: this.$pgettext('Content/Moderation/Dropdown', 'Other')
+            }
           },
           summary: {
-            label: this.$pgettext('Content/Account/*', 'Bio'),
+            label: this.$pgettext('Content/Account/*', 'Bio')
           },
           content_category: {
             label: this.$pgettext('Content/*/Dropdown.Label/Noun', 'Content category'),
             choices: {
               podcast: this.$pgettext('Content/*/Dropdown', 'Podcast'),
               music: this.$pgettext('*/*/*', 'Music'),
-              other: this.$pgettext('*/*/*', 'Other'),
-            },
+              other: this.$pgettext('*/*/*', 'Other')
+            }
           }
         },
         filters: {
@@ -89,56 +89,56 @@ export default {
           users: this.$pgettext('*/*/*/Noun', 'Users'),
           received_messages: this.$pgettext('Content/Moderation/*/Noun', 'Received messages'),
           uploads: this.$pgettext('*/*/*', 'Uploads'),
-          followers: this.$pgettext('Content/Federation/*/Noun', 'Followers'),
+          followers: this.$pgettext('Content/Federation/*/Noun', 'Followers')
         },
         scopes: {
           profile: {
             label: this.$pgettext('Content/OAuth Scopes/Label', 'Profile'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to e-mail, username, and profile information'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to e-mail, username, and profile information')
           },
           libraries: {
             label: this.$pgettext('Content/OAuth Scopes/Label', 'Libraries and uploads'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to audio files, libraries, artists, albums and tracks'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to audio files, libraries, artists, albums and tracks')
           },
           favorites: {
             label: this.$pgettext('Sidebar/Favorites/List item.Link/Noun', 'Favorites'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to favorites'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to favorites')
           },
           listenings: {
             label: this.$pgettext('*/*/*/Noun', 'Listenings'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to listening history'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to listening history')
           },
           follows: {
             label: this.$pgettext('Content/OAuth Scopes/Label', 'Follows'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to follows'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to follows')
           },
           playlists: {
             label: this.$pgettext('*/*/*', 'Playlists'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to playlists'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to playlists')
           },
           radios: {
             label: this.$pgettext('*/*/*', 'Radios'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to radios'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to radios')
           },
           filters: {
             label: this.$pgettext('Content/Settings/Title/Noun', 'Content filters'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to content filters'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to content filters')
           },
           notifications: {
             label: this.$pgettext('*/Notifications/*', 'Notifications'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to notifications'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to notifications')
           },
           edits: {
             label: this.$pgettext('*/Admin/*/Noun', 'Edits'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to edits'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to edits')
           },
           security: {
             label: this.$pgettext('*/Admin/*/Noun', 'Security'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to security settings such as password and authorization'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to security settings such as password and authorization')
           },
           reports: {
             label: this.$pgettext('*/Moderation/*/Noun', 'Reports'),
-            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to moderation reports'),
+            description: this.$pgettext('Content/OAuth Scopes/Paragraph', 'Access to moderation reports')
           }
         }
       }
diff --git a/front/src/components/moderation/FilterModal.vue b/front/src/components/moderation/FilterModal.vue
index f83e69741..981b82227 100644
--- a/front/src/components/moderation/FilterModal.vue
+++ b/front/src/components/moderation/FilterModal.vue
@@ -1,19 +1,37 @@
 <template>
-  <modal @update:show="update" :show="$store.state.moderation.showFilterModal">
+  <modal
+    :show="$store.state.moderation.showFilterModal"
+    @update:show="update"
+  >
     <h4 class="header">
       <translate
         v-if="type === 'artist'"
         key="1"
         translate-context="Popup/Moderation/Title/Verb"
-        :translate-params="{name: target.name}">Do you want to hide content from artist "%{ name }"?</translate>
+        :translate-params="{name: target.name}"
+      >
+        Do you want to hide content from artist "%{ name }"?
+      </translate>
     </h4>
     <div class="scrolling content">
       <div class="description">
-
-        <div v-if="errors.length > 0" role="alert" class="ui negative message">
-          <h4 class="header"><translate translate-context="Popup/Moderation/Error message">Error while creating filter</translate></h4>
+        <div
+          v-if="errors.length > 0"
+          role="alert"
+          class="ui negative message"
+        >
+          <h4 class="header">
+            <translate translate-context="Popup/Moderation/Error message">
+              Error while creating filter
+            </translate>
+          </h4>
           <ul class="list">
-            <li v-for="error in errors">{{ error }}</li>
+            <li
+              v-for="(error, key) in errors"
+              :key="key"
+            >
+              {{ error }}
+            </li>
           </ul>
         </div>
         <template v-if="type === 'artist'">
@@ -23,10 +41,26 @@
             </translate>
           </p>
           <ul>
-            <li><translate translate-context="Popup/Moderation/List item">In other users favorites and listening history</translate></li>
-            <li><translate translate-context="Popup/Moderation/List item">In "Recently added" widget</translate></li>
-            <li><translate translate-context="Popup/Moderation/List item">In artists and album listings</translate></li>
-            <li><translate translate-context="Popup/Moderation/List item">In radio suggestions</translate></li>
+            <li>
+              <translate translate-context="Popup/Moderation/List item">
+                In other users favorites and listening history
+              </translate>
+            </li>
+            <li>
+              <translate translate-context="Popup/Moderation/List item">
+                In "Recently added" widget
+              </translate>
+            </li>
+            <li>
+              <translate translate-context="Popup/Moderation/List item">
+                In artists and album listings
+              </translate>
+            </li>
+            <li>
+              <translate translate-context="Popup/Moderation/List item">
+                In radio suggestions
+              </translate>
+            </li>
           </ul>
           <p>
             <translate translate-context="Popup/Moderation/Paragraph">
@@ -37,23 +71,33 @@
       </div>
     </div>
     <div class="actions">
-      <button class="ui basic cancel button"><translate translate-context="*/*/Button.Label/Verb">Cancel</translate></button>
-      <button :class="['ui', 'success', {loading: isLoading}, 'button']" @click="hide"><translate translate-context="Popup/*/Button.Label">Hide content</translate></button>
+      <button class="ui basic cancel button">
+        <translate translate-context="*/*/Button.Label/Verb">
+          Cancel
+        </translate>
+      </button>
+      <button
+        :class="['ui', 'success', {loading: isLoading}, 'button']"
+        @click="hide"
+      >
+        <translate translate-context="Popup/*/Button.Label">
+          Hide content
+        </translate>
+      </button>
     </div>
   </modal>
 </template>
 
 <script>
-import _ from '@/lodash'
 import axios from 'axios'
-import {mapState} from 'vuex'
+import { mapState } from 'vuex'
 
 import logger from '@/logging'
 import Modal from '@/components/semantic/Modal'
 
 export default {
   components: {
-    Modal,
+    Modal
   },
   data () {
     return {
@@ -65,7 +109,7 @@ export default {
   computed: {
     ...mapState({
       type: state => state.moderation.filterModalTarget.type,
-      target: state => state.moderation.filterModalTarget.target,
+      target: state => state.moderation.filterModalTarget.target
     })
   },
   methods: {
@@ -74,12 +118,12 @@ export default {
       this.errors = []
     },
     hide () {
-      let self = this
+      const self = this
       self.isLoading = true
-      let payload = {
+      const payload = {
         target: {
           type: this.type,
-          id: this.target.id,
+          id: this.target.id
         }
       }
       return axios.post('moderation/content-filters/', payload).then(response => {
@@ -87,7 +131,7 @@ export default {
         self.update(false)
         self.$store.commit('moderation/lastUpdate', new Date())
         self.isLoading = false
-        let msg = this.$pgettext('*/Moderation/Message', 'Content filter successfully added')
+        const msg = this.$pgettext('*/Moderation/Message', 'Content filter successfully added')
         self.$store.commit('moderation/contentFilter', response.data)
         self.$store.commit('ui/addMessage', {
           content: msg,
diff --git a/front/src/components/moderation/ReportCategoryDropdown.vue b/front/src/components/moderation/ReportCategoryDropdown.vue
index 617f89b18..abab26b27 100644
--- a/front/src/components/moderation/ReportCategoryDropdown.vue
+++ b/front/src/components/moderation/ReportCategoryDropdown.vue
@@ -1,11 +1,26 @@
 <template>
   <div>
     <label v-if="label"><translate translate-context="*/*/*">Category</translate></label>
-    <select class="ui dropdown" :value="value" @change="$emit('input', $event.target.value)" :required="required">
-      <option v-if="empty" disabled value=''></option>
-      <option :value="option.value" v-for="option in allCategories">{{ option.label }}</option>
+    <select
+      class="ui dropdown"
+      :value="value"
+      :required="required"
+      @change="$emit('input', $event.target.value)"
+    >
+      <option
+        v-if="empty"
+        disabled
+        value=""
+      />
+      <option
+        v-for="(option, key) in allCategories"
+        :key="key"
+        :value="option.value"
+      >
+        {{ option.label }}
+      </option>
     </select>
-    <slot></slot>
+    <slot />
   </div>
 </template>
 
@@ -15,26 +30,26 @@ import lodash from '@/lodash'
 export default {
   mixins: [TranslationsMixin],
   props: {
-    value: {},
-    all: {},
-    label: {},
-    empty: {},
-    required: {},
-    restrictTo: {default: () => { return [] }}
+    value: { type: String, required: true },
+    all: { type: String, required: true },
+    label: { type: String, required: true },
+    empty: { type: String, required: true },
+    required: { type: String, required: true },
+    restrictTo: { type: Array, default: () => { return [] } }
   },
   computed: {
     allCategories () {
-      let c = []
+      const c = []
       if (this.all) {
         c.push(
           {
             value: '',
             label: this.$pgettext('Content/*/Dropdown', 'All')
-          },
+          }
         )
       }
       let choices
-      if (this.restrictTo.length > 0)  {
+      if (this.restrictTo.length > 0) {
         choices = this.restrictTo
       } else {
         choices = lodash.keys(this.sharedLabels.fields.report_type.choices)
diff --git a/front/src/components/moderation/ReportModal.vue b/front/src/components/moderation/ReportModal.vue
index bc308f5e2..a6fba132b 100644
--- a/front/src/components/moderation/ReportModal.vue
+++ b/front/src/components/moderation/ReportModal.vue
@@ -1,39 +1,79 @@
 <template>
-  <modal @update:show="update" :show="$store.state.moderation.showReportModal">
-    <h2 class="ui header" v-if="target">
-      <translate translate-context="Popup/Moderation/Title/Verb">Do you want to report this object?</translate>
+  <modal
+    :show="$store.state.moderation.showReportModal"
+    @update:show="update"
+  >
+    <h2
+      v-if="target"
+      class="ui header"
+    >
+      <translate translate-context="Popup/Moderation/Title/Verb">
+        Do you want to report this object?
+      </translate>
       <div class="ui sub header">
         {{ target.typeLabel }} - {{ target.label }}
       </div>
     </h2>
     <div class="scrolling content">
       <div class="description">
-        <div v-if="errors.length > 0" role="alert" class="ui negative message">
-          <h4 class="header"><translate translate-context="Popup/Moderation/Error message">Error while submitting report</translate></h4>
+        <div
+          v-if="errors.length > 0"
+          role="alert"
+          class="ui negative message"
+        >
+          <h4 class="header">
+            <translate translate-context="Popup/Moderation/Error message">
+              Error while submitting report
+            </translate>
+          </h4>
           <ul class="list">
-            <li v-for="error in errors">{{ error }}</li>
+            <li
+              v-for="(error, key) in errors"
+              :key="key"
+            >
+              {{ error }}
+            </li>
           </ul>
         </div>
       </div>
       <p>
-        <translate translate-context="*/Moderation/Popup,Paragraph">Use this form to submit a report to our moderation team.</translate>
+        <translate translate-context="*/Moderation/Popup,Paragraph">
+          Use this form to submit a report to our moderation team.
+        </translate>
       </p>
-      <form v-if="canSubmit" id="report-form" class="ui form" @submit.prevent="submit">
+      <form
+        v-if="canSubmit"
+        id="report-form"
+        class="ui form"
+        @submit.prevent="submit"
+      >
         <div class="fields">
           <report-category-dropdown
-            class="ui required eight wide field"
             v-model="category"
+            class="ui required eight wide field"
             :required="true"
             :empty="true"
             :restrict-to="allowedCategories"
-            :label="true"></report-category-dropdown>
-          <div v-if="!$store.state.auth.authenticated" class="ui eight wide required field">
+            :label="true"
+          />
+          <div
+            v-if="!$store.state.auth.authenticated"
+            class="ui eight wide required field"
+          >
             <label for="report-submitter-email">
               <translate translate-context="Content/*/*/Noun">Email</translate>
             </label>
-            <input type="email" v-model="submitterEmail" name="report-submitter-email" id="report-submitter-email" required>
+            <input
+              id="report-submitter-email"
+              v-model="submitterEmail"
+              type="email"
+              name="report-submitter-email"
+              required
+            >
             <p>
-              <translate translate-context="*/*/Field,Help">We'll use this e-mail address if we need to contact you regarding this report.</translate>
+              <translate translate-context="*/*/Field,Help">
+                We'll use this e-mail address if we need to contact you regarding this report.
+              </translate>
             </p>
           </div>
         </div>
@@ -42,16 +82,32 @@
             <translate translate-context="*/*/Field.Label/Noun">Message</translate>
           </label>
           <p>
-            <translate translate-context="*/*/Field,Help">Use this field to provide additional context to the moderator that will handle your report.</translate>
+            <translate translate-context="*/*/Field,Help">
+              Use this field to provide additional context to the moderator that will handle your report.
+            </translate>
           </p>
-          <content-form field-id="report-summary" :rows="8" v-model="summary"></content-form>
+          <content-form
+            v-model="summary"
+            field-id="report-summary"
+            :rows="8"
+          />
         </div>
-        <div class="ui field" v-if="!isLocal">
+        <div
+          v-if="!isLocal"
+          class="ui field"
+        >
           <div class="ui checkbox">
-            <input id="report-forward" v-model="forward" type="checkbox">
+            <input
+              id="report-forward"
+              v-model="forward"
+              type="checkbox"
+            >
             <label for="report-forward">
               <strong>
-                <translate :translate-params="{domain: targetDomain}" translate-context="*/*/Field.Label/Verb">Forward to %{ domain} </translate>
+                <translate
+                  :translate-params="{domain: targetDomain}"
+                  translate-context="*/*/Field.Label/Verb"
+                >Forward to %{ domain} </translate>
               </strong>
               <p>
                 <translate translate-context="*/*/Field,Help">Forward an anonymized copy of your report to the server hosting this element.</translate>
@@ -59,46 +115,58 @@
             </label>
           </div>
         </div>
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
       </form>
-      <div v-else-if="isLoadingReportTypes" class="ui inline active loader">
-
-      </div>
-      <div v-else role="alert" class="ui warning message">
+      <div
+        v-else-if="isLoadingReportTypes"
+        class="ui inline active loader"
+      />
+      <div
+        v-else
+        role="alert"
+        class="ui warning message"
+      >
         <h4 class="header">
-          <translate translate-context="Popup/Moderation/Error message">Anonymous reports are disabled, please sign-in to submit a report.</translate>
+          <translate translate-context="Popup/Moderation/Error message">
+            Anonymous reports are disabled, please sign-in to submit a report.
+          </translate>
         </h4>
       </div>
     </div>
     <div class="actions">
-      <button class="ui basic cancel button"><translate translate-context="*/*/Button.Label/Verb">Cancel</translate></button>
+      <button class="ui basic cancel button">
+        <translate translate-context="*/*/Button.Label/Verb">
+          Cancel
+        </translate>
+      </button>
       <button
         v-if="canSubmit"
         :class="['ui', 'success', {loading: isLoading}, 'button']"
-        type="submit" form="report-form">
-        <translate translate-context="Popup/*/Button.Label">Submit report</translate>
+        type="submit"
+        form="report-form"
+      >
+        <translate translate-context="Popup/*/Button.Label">
+          Submit report
+        </translate>
       </button>
     </div>
   </modal>
 </template>
 
 <script>
-import _ from '@/lodash'
 import axios from 'axios'
-import {mapState} from 'vuex'
+import { mapState } from 'vuex'
 
-import logger from '@/logging'
-
-function urlDomain(data) {
-  var    a      = document.createElement('a');
-         a.href = data;
-  return a.hostname;
+function urlDomain (data) {
+  const a = document.createElement('a')
+  a.href = data
+  return a.hostname
 }
 
 export default {
   components: {
-    ReportCategoryDropdown:  () => import(/* webpackChunkName: "reports" */ "@/components/moderation/ReportCategoryDropdown"),
-    Modal:  () => import(/* webpackChunkName: "modal" */ "@/components/semantic/Modal"),
+    ReportCategoryDropdown: () => import(/* webpackChunkName: "reports" */ '@/components/moderation/ReportCategoryDropdown'),
+    Modal: () => import(/* webpackChunkName: "modal" */ '@/components/semantic/Modal')
   },
   data () {
     return {
@@ -110,12 +178,12 @@ export default {
       submitterEmail: '',
       category: null,
       reportTypes: [],
-      forward: false,
+      forward: false
     }
   },
   computed: {
     ...mapState({
-      target: state => state.moderation.reportModalTarget,
+      target: state => state.moderation.reportModalTarget
     }),
     allowedCategories () {
       if (this.$store.state.auth.authenticated) {
@@ -126,7 +194,6 @@ export default {
       }).map((c) => {
         return c.type
       })
-
     },
     canSubmit () {
       if (this.$store.state.auth.authenticated) {
@@ -140,7 +207,7 @@ export default {
         return
       }
       let fid = this.target._obj.fid
-      if (this.target.type === 'channel' && this.target._obj.actor ) {
+      if (this.target.type === 'channel' && this.target._obj.actor) {
         fid = this.target._obj.actor.fid
       }
       if (!fid) {
@@ -152,19 +219,39 @@ export default {
       return this.$store.getters['instance/domain'] === this.targetDomain
     }
   },
+  watch: {
+    '$store.state.moderation.showReportModal': function (v) {
+      if (!v || this.$store.state.auth.authenticated) {
+        return
+      }
+
+      const self = this
+      self.isLoadingReportTypes = true
+      axios.get('instance/nodeinfo/2.0/').then(response => {
+        self.isLoadingReportTypes = false
+        self.reportTypes = response.data.metadata.reportTypes || []
+      }, error => {
+        self.isLoadingReportTypes = false
+        self.$store.commit('ui/addMessage', {
+          content: 'Cannot fetch Node Info: ' + error,
+          date: new Date()
+        })
+      })
+    }
+  },
   methods: {
     update (v) {
       this.$store.commit('moderation/showReportModal', v)
       this.errors = []
     },
     submit () {
-      let self = this
+      const self = this
       self.isLoading = true
-      let payload = {
-        target: {...this.target, _obj: null},
+      const payload = {
+        target: { ...this.target, _obj: null },
         summary: this.summary,
         type: this.category,
-        forward: this.forward,
+        forward: this.forward
       }
       if (!this.$store.state.auth.authenticated) {
         payload.submitter_email = this.submitterEmail
@@ -172,7 +259,7 @@ export default {
       return axios.post('moderation/reports/', payload).then(response => {
         self.update(false)
         self.isLoading = false
-        let msg = this.$pgettext('*/Moderation/Message', 'Report successfully submitted, thank you')
+        const msg = this.$pgettext('*/Moderation/Message', 'Report successfully submitted, thank you')
         self.$store.commit('moderation/contentFilter', response.data)
         self.$store.commit('ui/addMessage', {
           content: msg,
@@ -185,22 +272,6 @@ export default {
         self.isLoading = false
       })
     }
-  },
-  watch: {
-    '$store.state.moderation.showReportModal': function (v) {
-      if (!v || this.$store.state.auth.authenticated) {
-        return
-      }
-
-      let self = this
-      self.isLoadingReportTypes = true
-      axios.get('instance/nodeinfo/2.0/').then(response => {
-        self.isLoadingReportTypes = false
-        self.reportTypes = response.data.metadata.reportTypes || []
-      }, error => {
-        self.isLoadingReportTypes = false
-      })
-    }
   }
 }
 </script>
diff --git a/front/src/components/notifications/NotificationRow.vue b/front/src/components/notifications/NotificationRow.vue
index 68a017d17..3eaa16d96 100644
--- a/front/src/components/notifications/NotificationRow.vue
+++ b/front/src/components/notifications/NotificationRow.vue
@@ -1,30 +1,67 @@
 <template>
   <tr :class="[{'disabled-row': item.is_read}]">
     <td>
-      <actor-link class="user" :actor="item.activity.actor" />
+      <actor-link
+        class="user"
+        :actor="item.activity.actor"
+      />
     </td>
     <td>
-      <router-link tag="span" class="link" v-if="notificationData.detailUrl" :to="notificationData.detailUrl" v-html="notificationData.message">
-
-      </router-link>
-      <template v-else v-html="notificationData.message"></template>
-      <template v-if="notificationData.acceptFollow">&nbsp;
-        <button @click="handleAction(notificationData.acceptFollow.handler)" :class="['ui', 'basic', 'tiny', notificationData.acceptFollow.buttonClass || '', 'button']">
-          <i v-if="notificationData.acceptFollow.icon" :class="[notificationData.acceptFollow.icon, 'icon']" />
+      <router-link
+        v-if="notificationData.detailUrl"
+        tag="span"
+        class="link"
+        :to="notificationData.detailUrl"
+        v-html="notificationData.message"
+      />
+      <template
+        v-else
+        v-html="notificationData.message"
+      />
+      <template v-if="notificationData.acceptFollow">
+&nbsp;
+        <button
+          :class="['ui', 'basic', 'tiny', notificationData.acceptFollow.buttonClass || '', 'button']"
+          @click="handleAction(notificationData.acceptFollow.handler)"
+        >
+          <i
+            v-if="notificationData.acceptFollow.icon"
+            :class="[notificationData.acceptFollow.icon, 'icon']"
+          />
           {{ notificationData.acceptFollow.label }}
         </button>
-        <button @click="handleAction(notificationData.rejectFollow.handler)" :class="['ui', 'basic', 'tiny', notificationData.rejectFollow.buttonClass || '', 'button']">
-          <i v-if="notificationData.rejectFollow.icon" :class="[notificationData.rejectFollow.icon, 'icon']" />
+        <button
+          :class="['ui', 'basic', 'tiny', notificationData.rejectFollow.buttonClass || '', 'button']"
+          @click="handleAction(notificationData.rejectFollow.handler)"
+        >
+          <i
+            v-if="notificationData.rejectFollow.icon"
+            :class="[notificationData.rejectFollow.icon, 'icon']"
+          />
           {{ notificationData.rejectFollow.label }}
         </button>
       </template>
     </td>
     <td><human-date :date="item.activity.creation_date" /></td>
     <td class="read collapsing">
-      <a href="" :aria-label="labels.markUnread" @click.prevent="markRead(false)" class="discrete link" v-if="item.is_read" :title="labels.markUnread">
+      <a
+        v-if="item.is_read"
+        href=""
+        :aria-label="labels.markUnread"
+        class="discrete link"
+        :title="labels.markUnread"
+        @click.prevent="markRead(false)"
+      >
         <i class="redo icon" />
       </a>
-      <a href="" :aria-label="labels.markRead" @click.prevent="markRead(true)" class="discrete link" v-else :title="labels.markRead">
+      <a
+        v-else
+        href=""
+        :aria-label="labels.markRead"
+        class="discrete link"
+        :title="labels.markRead"
+        @click.prevent="markRead(true)"
+      >
         <i class="check icon" />
       </a>
     </td>
@@ -34,23 +71,28 @@
 import axios from 'axios'
 
 export default {
-  props: ['item'],
+  props: { initialItem: { type: Object, required: true } },
+  data: function () {
+    return {
+      item: this.initialItem
+    }
+  },
   computed: {
     message () {
       return 'plop'
     },
     labels () {
-      let libraryFollowMessage = this.$pgettext('Content/Notifications/Paragraph', '%{ username } followed your library "%{ library }"')
-      let libraryAcceptFollowMessage = this.$pgettext('Content/Notifications/Paragraph', '%{ username } accepted your follow on library "%{ library }"')
-      let libraryRejectMessage = this.$pgettext('Content/Notifications/Paragraph', 'You rejected %{ username }&#39;s request to follow "%{ library }"')
-      let libraryPendingFollowMessage = this.$pgettext('Content/Notifications/Paragraph', '%{ username } wants to follow your library "%{ library }"')
+      const libraryFollowMessage = this.$pgettext('Content/Notifications/Paragraph', '%{ username } followed your library "%{ library }"')
+      const libraryAcceptFollowMessage = this.$pgettext('Content/Notifications/Paragraph', '%{ username } accepted your follow on library "%{ library }"')
+      const libraryRejectMessage = this.$pgettext('Content/Notifications/Paragraph', 'You rejected %{ username }&#39;s request to follow "%{ library }"')
+      const libraryPendingFollowMessage = this.$pgettext('Content/Notifications/Paragraph', '%{ username } wants to follow your library "%{ library }"')
       return {
         libraryFollowMessage,
         libraryAcceptFollowMessage,
         libraryRejectMessage,
         libraryPendingFollowMessage,
         markRead: this.$pgettext('Content/Notifications/Button.Tooltip/Verb', 'Mark as read'),
-        markUnread: this.$pgettext('Content/Notifications/Button.Tooltip/Verb', 'Mark as unread'),
+        markUnread: this.$pgettext('Content/Notifications/Button.Tooltip/Verb', 'Mark as unread')
 
       }
     },
@@ -58,8 +100,8 @@ export default {
       return this.item.activity.actor.preferred_username
     },
     notificationData () {
-      let self = this
-      let a = this.item.activity
+      const self = this
+      const a = this.item.activity
       if (a.type === 'Follow') {
         if (a.object && a.object.type === 'music.Library') {
           let acceptFollow = null
@@ -72,7 +114,7 @@ export default {
               icon: 'check',
               label: this.$pgettext('Content/*/Button.Label/Verb', 'Approve'),
               handler: () => { self.approveLibraryFollow(a.related_object) }
-            },
+            }
             rejectFollow = {
               buttonClass: 'danger',
               icon: 'x',
@@ -87,10 +129,10 @@ export default {
           return {
             acceptFollow,
             rejectFollow,
-            detailUrl: {name: 'content.libraries.detail', params: {id: a.object.uuid}},
+            detailUrl: { name: 'content.libraries.detail', params: { id: a.object.uuid } },
             message: this.$gettextInterpolate(
               message,
-              {username: this.username, library: a.object.name}
+              { username: this.username, library: a.object.name }
             )
           }
         }
@@ -98,10 +140,10 @@ export default {
       if (a.type === 'Accept') {
         if (a.object && a.object.type === 'federation.LibraryFollow') {
           return {
-            detailUrl: {name: 'content.remote.index'},
+            detailUrl: { name: 'content.remote.index' },
             message: this.$gettextInterpolate(
               this.labels.libraryAcceptFollowMessage,
-              {username: this.username, library: a.related_object.name}
+              { username: this.username, library: a.related_object.name }
             )
           }
         }
@@ -116,30 +158,27 @@ export default {
       this.markRead(true)
     },
     approveLibraryFollow (follow) {
-      let self = this
-      let action = 'accept'
+      const action = 'accept'
       axios.post(`federation/follows/library/${follow.uuid}/${action}/`).then((response) => {
         follow.isLoading = false
         follow.approved = true
       })
     },
     rejectLibraryFollow (follow) {
-      let self = this
-      let action = 'reject'
+      const action = 'reject'
       axios.post(`federation/follows/library/${follow.uuid}/${action}/`).then((response) => {
         follow.isLoading = false
         follow.approved = false
       })
     },
     markRead (value) {
-      let self = this
-      let action = 'accept'
-      axios.patch(`federation/inbox/${this.item.id}/`, {is_read: value}).then((response) => {
+      const self = this
+      axios.patch(`federation/inbox/${this.item.id}/`, { is_read: value }).then((response) => {
         self.item.is_read = value
         if (value) {
-          self.$store.commit('ui/incrementNotifications', {type: 'inbox', count: -1})
+          self.$store.commit('ui/incrementNotifications', { type: 'inbox', count: -1 })
         } else {
-          self.$store.commit('ui/incrementNotifications', {type: 'inbox', count: 1})
+          self.$store.commit('ui/incrementNotifications', { type: 'inbox', count: 1 })
         }
       })
     }
diff --git a/front/src/components/playlists/Card.vue b/front/src/components/playlists/Card.vue
index c37f24289..d02aac56b 100644
--- a/front/src/components/playlists/Card.vue
+++ b/front/src/components/playlists/Card.vue
@@ -1,24 +1,55 @@
 <template>
   <div class="ui app-card card">
     <div
+      :class="['ui', 'head-image', 'squares']"
       @click="$router.push({name: 'library.playlists.detail', params: {id: playlist.id }})"
-      :class="['ui', 'head-image', 'squares']">
-      <img alt="" v-lazy="url" v-for="(url, idx) in images" :key="idx" />
-      <play-button :icon-only="true" :is-playable="playlist.is_playable" :button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']" :playlist="playlist"></play-button>
+    >
+      <img
+        v-for="(url, idx) in images"
+        :key="idx"
+        v-lazy="url"
+        alt=""
+      >
+      <play-button
+        :icon-only="true"
+        :is-playable="playlist.is_playable"
+        :button-classes="['ui', 'circular', 'large', 'vibrant', 'icon', 'button']"
+        :playlist="playlist"
+      />
     </div>
     <div class="content">
       <strong>
-        <router-link class="discrete link" :to="{name: 'library.playlists.detail', params: {id: playlist.id }}">
+        <router-link
+          class="discrete link"
+          :to="{name: 'library.playlists.detail', params: {id: playlist.id }}"
+        >
           {{ playlist.name }}
         </router-link>
       </strong>
       <div class="description">
-        <user-link :user="playlist.user" :avatar="false" class="left floated" />
+        <user-link
+          :user="playlist.user"
+          :avatar="false"
+          class="left floated"
+        />
       </div>
     </div>
     <div class="extra content">
-      <translate translate-context="*/*/*" :translate-params="{count: playlist.tracks_count}" :translate-n="playlist.tracks_count" translate-plural="%{ count } tracks">%{ count } track</translate>
-      <play-button class="right floated basic icon" :dropdown-only="true" :is-playable="playlist.is_playable" :dropdown-icon-classes="['ellipsis', 'horizontal', 'large really discrete']" :playlist="playlist"></play-button>
+      <translate
+        translate-context="*/*/*"
+        :translate-params="{count: playlist.tracks_count}"
+        :translate-n="playlist.tracks_count"
+        translate-plural="%{ count } tracks"
+      >
+        %{ count } track
+      </translate>
+      <play-button
+        class="right floated basic icon"
+        :dropdown-only="true"
+        :is-playable="playlist.is_playable"
+        :dropdown-icon-classes="['ellipsis', 'horizontal', 'large really discrete']"
+        :playlist="playlist"
+      />
     </div>
   </div>
 </template>
@@ -27,14 +58,14 @@
 import PlayButton from '@/components/audio/PlayButton'
 
 export default {
-  props: ['playlist'],
   components: {
     PlayButton
   },
+  props: { playlist: { type: Object, required: true } },
   computed: {
     images () {
-      let self = this
-      let urls = this.playlist.album_covers.map((url) => {
+      const self = this
+      const urls = this.playlist.album_covers.map((url) => {
         return self.$store.getters['instance/absoluteUrl'](url)
       }).slice(0, 4)
       while (urls.length < 4) {
diff --git a/front/src/components/playlists/CardList.vue b/front/src/components/playlists/CardList.vue
index fe9d91620..e3c17e636 100644
--- a/front/src/components/playlists/CardList.vue
+++ b/front/src/components/playlists/CardList.vue
@@ -2,10 +2,10 @@
   <div v-if="playlists.length > 0">
     <div class="ui app-cards cards">
       <playlist-card
-        :playlist="playlist"
         v-for="playlist in playlists"
         :key="playlist.id"
-      ></playlist-card>
+        :playlist="playlist"
+      />
     </div>
   </div>
 </template>
@@ -15,9 +15,9 @@
 import PlaylistCard from '@/components/playlists/Card'
 
 export default {
-  props: ['playlists'],
   components: {
     PlaylistCard
-  }
+  },
+  props: { playlists: { type: Array, required: true } }
 }
 </script>
diff --git a/front/src/components/playlists/Editor.vue b/front/src/components/playlists/Editor.vue
index a088e1737..68ce6a10c 100644
--- a/front/src/components/playlists/Editor.vue
+++ b/front/src/components/playlists/Editor.vue
@@ -1,85 +1,172 @@
 <template>
   <div class="ui text container component-playlist-editor">
-    <playlist-form @updated="$emit('playlist-updated', $event)" :title="false" :playlist="playlist"></playlist-form>
+    <playlist-form
+      :title="false"
+      :playlist="playlist"
+      @updated="$emit('playlist-updated', $event)"
+    />
     <h3 class="ui top attached header">
-      <translate translate-context="Content/Playlist/Title">Playlist editor</translate>
+      <translate translate-context="Content/Playlist/Title">
+        Playlist editor
+      </translate>
     </h3>
     <div class="ui attached segment">
       <template v-if="status === 'loading'">
-        <div class="ui active tiny inline loader"></div>
-        <translate translate-context="Content/Playlist/Paragraph">Syncing changes to server…</translate>
+        <div class="ui active tiny inline loader" />
+        <translate translate-context="Content/Playlist/Paragraph">
+          Syncing changes to server…
+        </translate>
       </template>
       <template v-else-if="status === 'errored'">
-        <i class="dangerclose icon"></i>
-        <translate translate-context="Content/Playlist/Error message.Title">An error occurred while saving your changes</translate>
-        <div v-if="errors.length > 0" role="alert" class="ui negative message">
+        <i class="dangerclose icon" />
+        <translate translate-context="Content/Playlist/Error message.Title">
+          An error occurred while saving your changes
+        </translate>
+        <div
+          v-if="errors.length > 0"
+          role="alert"
+          class="ui negative message"
+        >
           <ul class="list">
-            <li v-for="error in errors">{{ error }}</li>
+            <li
+              v-for="(error, key) in errors"
+              :key="key"
+            >
+              {{ error }}
+            </li>
           </ul>
         </div>
       </template>
-      <div v-else-if="status === 'confirmDuplicateAdd'" role="alert" class="ui warning message">
-        <p translate-context="Content/Playlist/Paragraph"
-            v-translate="{playlist: playlist.name}">Some tracks in your queue are already in this playlist:</p>
+      <div
+        v-else-if="status === 'confirmDuplicateAdd'"
+        role="alert"
+        class="ui warning message"
+      >
+        <p
+          v-translate="{playlist: playlist.name}"
+          translate-context="Content/Playlist/Paragraph"
+        >
+          Some tracks in your queue are already in this playlist:
+        </p>
         <ul class="ui relaxed divided list duplicate-tracks-list">
-          <li v-for="track in duplicateTrackAddInfo.tracks" class="ui item">{{ track }}</li>
+          <li
+            v-for="(track, key) in duplicateTrackAddInfo.tracks"
+            :key="key"
+            class="ui item"
+          >
+            {{ track }}
+          </li>
         </ul>
         <button
           class="ui small success button"
-          @click="insertMany(queueTracks, true)"><translate translate-context="*/Playlist/Button.Label/Verb">Add anyways</translate></button>
+          @click="insertMany(queueTracks, true)"
+        >
+          <translate translate-context="*/Playlist/Button.Label/Verb">
+            Add anyways
+          </translate>
+        </button>
       </div>
       <template v-else-if="status === 'saved'">
-        <i class="success check icon"></i> <translate translate-context="Content/Playlist/Paragraph">Changes synced with server</translate>
+        <i class="success check icon" /> <translate translate-context="Content/Playlist/Paragraph">
+          Changes synced with server
+        </translate>
       </template>
     </div>
     <div class="ui bottom attached segment">
       <button
-        @click="insertMany(queueTracks, false)"
         :disabled="queueTracks.length === 0"
         :class="['ui', {disabled: queueTracks.length === 0}, 'labeled', 'icon', 'button']"
-        :title="labels.copyTitle">
-          <i class="plus icon"></i>
-          <translate translate-context="Content/Playlist/Button.Label/Verb"
-            translate-plural="Insert from queue (%{ count } tracks)"
-            :translate-n="queueTracks.length"
-            :translate-params="{count: queueTracks.length}">
-            Insert from queue (%{ count } track)
-          </translate>
-        </button>
+        :title="labels.copyTitle"
+        @click="insertMany(queueTracks, false)"
+      >
+        <i class="plus icon" />
+        <translate
+          translate-context="Content/Playlist/Button.Label/Verb"
+          translate-plural="Insert from queue (%{ count } tracks)"
+          :translate-n="queueTracks.length"
+          :translate-params="{count: queueTracks.length}"
+        >
+          Insert from queue (%{ count } track)
+        </translate>
+      </button>
 
-      <dangerous-button :disabled="plts.length === 0" class="ui labeled right floated danger icon button" :action="clearPlaylist">
-        <i class="eraser icon"></i> <translate translate-context="*/Playlist/Button.Label/Verb">Clear playlist</translate>
-        <p slot="modal-header" v-translate="{playlist: playlist.name}" translate-context="Popup/Playlist/Title"  :translate-params="{playlist: playlist.name}">
+      <dangerous-button
+        :disabled="plts.length === 0"
+        class="ui labeled right floated danger icon button"
+        :action="clearPlaylist"
+      >
+        <i class="eraser icon" /> <translate translate-context="*/Playlist/Button.Label/Verb">
+          Clear playlist
+        </translate>
+        <p
+          slot="modal-header"
+          v-translate="{playlist: playlist.name}"
+          translate-context="Popup/Playlist/Title"
+          :translate-params="{playlist: playlist.name}"
+        >
           Do you want to clear the playlist "%{ playlist }"?
         </p>
-        <p slot="modal-content"><translate translate-context="Popup/Playlist/Paragraph">This will remove all tracks from this playlist and cannot be undone.</translate></p>
-        <div slot="modal-confirm"><translate translate-context="*/Playlist/Button.Label/Verb">Clear playlist</translate></div>
+        <p slot="modal-content">
+          <translate translate-context="Popup/Playlist/Paragraph">
+            This will remove all tracks from this playlist and cannot be undone.
+          </translate>
+        </p>
+        <div slot="modal-confirm">
+          <translate translate-context="*/Playlist/Button.Label/Verb">
+            Clear playlist
+          </translate>
+        </div>
       </dangerous-button>
-      <div class="ui hidden divider"></div>
+      <div class="ui hidden divider" />
       <template v-if="plts.length > 0">
-        <p><translate translate-context="Content/Playlist/Paragraph/Call to action">Drag and drop rows to reorder tracks in the playlist</translate></p>
+        <p>
+          <translate translate-context="Content/Playlist/Paragraph/Call to action">
+            Drag and drop rows to reorder tracks in the playlist
+          </translate>
+        </p>
         <div class="table-wrapper">
           <table class="ui compact very basic unstackable table">
-            <draggable v-model="plts" tag="tbody" @update="reorder">
-              <tr v-for="(plt, index) in plts" :key="`${index}-${plt.track.id}`">
-                <td class="left aligned">{{ plt.index + 1}}</td>
+            <draggable
+              v-model="plts"
+              tag="tbody"
+              @update="reorder"
+            >
+              <tr
+                v-for="(plt, index) in plts"
+                :key="`${index}-${plt.track.id}`"
+              >
+                <td class="left aligned">
+                  {{ plt.index + 1 }}
+                </td>
                 <td class="center aligned">
-                  <img alt="" class="ui mini image" v-if="plt.track.album && plt.track.album.cover && plt.track.album.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](plt.track.album.cover.urls.medium_square_crop)">
-                  <img alt="" class="ui mini image" v-else src="../../assets/audio/default-cover.png">
+                  <img
+                    v-if="plt.track.album && plt.track.album.cover && plt.track.album.cover.urls.original"
+                    v-lazy="$store.getters['instance/absoluteUrl'](plt.track.album.cover.urls.medium_square_crop)"
+                    alt=""
+                    class="ui mini image"
+                  >
+                  <img
+                    v-else
+                    alt=""
+                    class="ui mini image"
+                    src="../../assets/audio/default-cover.png"
+                  >
                 </td>
                 <td colspan="4">
-                  <strong>{{ plt.track.title }}</strong><br />
-                    {{ plt.track.artist.name }}
+                  <strong>{{ plt.track.title }}</strong><br>
+                  {{ plt.track.artist.name }}
                 </td>
                 <td class="right aligned">
                   <button class="ui circular danger basic icon button">
-                    <i @click.stop="removePlt(index)" class="trash icon"></i>                
+                    <i
+                      class="trash icon"
+                      @click.stop="removePlt(index)"
+                    />
                   </button>
                 </td>
               </tr>
             </draggable>
           </table>
-
         </div>
       </template>
     </div>
@@ -87,7 +174,7 @@
 </template>
 
 <script>
-import {mapState} from 'vuex'
+import { mapState } from 'vuex'
 import axios from 'axios'
 import PlaylistForm from '@/components/playlists/Form'
 
@@ -98,7 +185,10 @@ export default {
     draggable,
     PlaylistForm
   },
-  props: ['playlist', 'playlistTracks'],
+  props: {
+    playlist: { type: Object, required: true },
+    playlistTracks: { type: Array, required: true }
+  },
   data () {
     return {
       plts: this.playlistTracks,
@@ -108,6 +198,39 @@ export default {
       showDuplicateTrackAddConfirmation: false
     }
   },
+  computed: {
+    ...mapState({
+      queueTracks: state => state.queue.tracks
+    }),
+    labels () {
+      return {
+        copyTitle: this.$pgettext('Content/Playlist/Button.Tooltip/Verb', 'Copy the current queue to this playlist')
+      }
+    },
+    status () {
+      if (this.isLoading) {
+        return 'loading'
+      }
+      if (this.errors.length > 0) {
+        return 'errored'
+      }
+      if (this.showDuplicateTrackAddConfirmation) {
+        return 'confirmDuplicateAdd'
+      }
+      return 'saved'
+    }
+  },
+  watch: {
+    plts: {
+      handler (newValue) {
+        newValue.forEach((e, i) => {
+          e.index = i
+        })
+        this.$emit('tracks-updated', newValue)
+      },
+      deep: true
+    }
+  },
   methods: {
     success () {
       this.isLoading = false
@@ -116,19 +239,18 @@ export default {
     },
     errored (errors) {
       this.isLoading = false
-      if (errors.length == 1 && errors[0].code == 'tracks_already_exist_in_playlist') {
+      if (errors.length === 1 && errors[0].code === 'tracks_already_exist_in_playlist') {
         this.duplicateTrackAddInfo = errors[0]
         this.showDuplicateTrackAddConfirmation = true
       } else {
         this.errors = errors
       }
     },
-    reorder ({oldIndex, newIndex}) {
-      let self = this
+    reorder ({ oldIndex, newIndex }) {
+      const self = this
       self.isLoading = true
-      let plt = this.plts[newIndex]
-      let url = `playlists/${this.playlist.id}/move`
-      axios.post(url, {from: oldIndex, to: newIndex}).then((response) => {
+      const url = `playlists/${this.playlist.id}/move`
+      axios.post(url, { from: oldIndex, to: newIndex }).then((response) => {
         self.success()
       }, error => {
         self.errored(error.backendErrors)
@@ -136,10 +258,10 @@ export default {
     },
     removePlt (index) {
       this.plts.splice(index, 1)
-      let self = this
+      const self = this
       self.isLoading = true
-      let url = `playlists/${this.playlist.id}/remove`
-      axios.post(url, {index}).then((response) => {
+      const url = `playlists/${this.playlist.id}/remove`
+      axios.post(url, { index }).then((response) => {
         self.success()
         self.$store.dispatch('playlists/fetchOwn')
       }, error => {
@@ -148,9 +270,9 @@ export default {
     },
     clearPlaylist () {
       this.plts = []
-      let self = this
+      const self = this
       self.isLoading = true
-      let url = 'playlists/' + this.playlist.id + '/clear'
+      const url = 'playlists/' + this.playlist.id + '/clear'
       axios.delete(url).then((response) => {
         self.success()
         self.$store.dispatch('playlists/fetchOwn')
@@ -159,16 +281,16 @@ export default {
       })
     },
     insertMany (tracks, allowDuplicates) {
-      let self = this
-      let ids = tracks.map(t => {
+      const self = this
+      const ids = tracks.map(t => {
         return t.id
       })
-      let payload = {
+      const payload = {
         tracks: ids,
         allow_duplicates: allowDuplicates
       }
       self.isLoading = true
-      let url = 'playlists/' + this.playlist.id + '/add/'
+      const url = 'playlists/' + this.playlist.id + '/add/'
       axios.post(url, payload).then((response) => {
         response.data.results.forEach(r => {
           self.plts.push(r)
@@ -185,39 +307,6 @@ export default {
         }
       })
     }
-  },
-  computed: {
-    ...mapState({
-      queueTracks: state => state.queue.tracks
-    }),
-    labels () {
-      return {
-        copyTitle: this.$pgettext('Content/Playlist/Button.Tooltip/Verb', 'Copy the current queue to this playlist')
-      }
-    },
-    status () {
-      if (this.isLoading) {
-        return 'loading'
-      }
-      if (this.errors.length > 0) {
-        return 'errored'
-      }
-      if (this.showDuplicateTrackAddConfirmation) {
-        return 'confirmDuplicateAdd'
-      }
-      return 'saved'
-    }
-  },
-  watch: {
-    plts: {
-      handler (newValue) {
-        newValue.forEach((e, i) => {
-          e.index = i
-        })
-        this.$emit('tracks-updated', newValue)
-      },
-      deep: true
-    }
   }
 }
 </script>
diff --git a/front/src/components/playlists/Form.vue b/front/src/components/playlists/Form.vue
index 757703dfd..9d5301388 100644
--- a/front/src/components/playlists/Form.vue
+++ b/front/src/components/playlists/Form.vue
@@ -1,38 +1,96 @@
 <template>
-  <form class="ui form" @submit.prevent="submit()">
-    <h4 v-if="title" class="ui header"><translate translate-context="Popup/Playlist/Title/Verb">Create a new playlist</translate></h4>
-    <div v-if="success" class="ui positive message">
+  <form
+    class="ui form"
+    @submit.prevent="submit()"
+  >
+    <h4
+      v-if="title"
+      class="ui header"
+    >
+      <translate translate-context="Popup/Playlist/Title/Verb">
+        Create a new playlist
+      </translate>
+    </h4>
+    <div
+      v-if="success"
+      class="ui positive message"
+    >
       <h4 class="header">
         <template v-if="playlist">
-          <translate translate-context="Content/Playlist/Message">Playlist updated</translate>
+          <translate translate-context="Content/Playlist/Message">
+            Playlist updated
+          </translate>
         </template>
         <template v-else>
-          <translate translate-context="Content/Playlist/Message">Playlist created</translate>
+          <translate translate-context="Content/Playlist/Message">
+            Playlist created
+          </translate>
         </template>
       </h4>
     </div>
-    <div v-if="errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/Playlist/Error message.Title">The playlist could not be created</translate></h4>
+    <div
+      v-if="errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/Playlist/Error message.Title">
+          The playlist could not be created
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
     <div class="three fields">
       <div class="field">
         <label for="playlist-name"><translate translate-context="Content/Playlist/Input.Label">Playlist name</translate></label>
-        <input id ="playlist-name" name="name" v-model="name" required type="text" :placeholder="labels.placeholder" />
+        <input
+          id="playlist-name"
+          v-model="name"
+          name="name"
+          required
+          type="text"
+          :placeholder="labels.placeholder"
+        >
       </div>
       <div class="field">
         <label for="playlist-visibility"><translate translate-context="Content/Playlist/Dropdown.Label">Playlist visibility</translate></label>
-        <select id="playlist-visibility" class="ui dropdown" v-model="privacyLevel">
-          <option :value="c.value" v-for="c in privacyLevelChoices">{{ c.label }}</option>
+        <select
+          id="playlist-visibility"
+          v-model="privacyLevel"
+          class="ui dropdown"
+        >
+          <option
+            v-for="(c, key) in privacyLevelChoices"
+            :key="key"
+            :value="c.value"
+          >
+            {{ c.label }}
+          </option>
         </select>
       </div>
       <div class="field">
-        <span id="updatePlaylistLabel"></span>
-        <button :class="['ui', 'fluid', {'loading': isLoading}, 'button']" type="submit">
-          <template v-if="playlist"><translate translate-context="Content/Playlist/Button.Label/Verb">Update playlist</translate></template>
-          <template v-else><translate translate-context="Content/Playlist/Button.Label/Verb">Create playlist</translate></template>
+        <span id="updatePlaylistLabel" />
+        <button
+          :class="['ui', 'fluid', {'loading': isLoading}, 'button']"
+          type="submit"
+        >
+          <template v-if="playlist">
+            <translate translate-context="Content/Playlist/Button.Label/Verb">
+              Update playlist
+            </translate>
+          </template>
+          <template v-else>
+            <translate translate-context="Content/Playlist/Button.Label/Verb">
+              Create playlist
+            </translate>
+          </template>
         </button>
       </div>
     </div>
@@ -42,21 +100,18 @@
 <script>
 import $ from 'jquery'
 import axios from 'axios'
-import TranslationsMixin from "@/components/mixins/Translations"
+import TranslationsMixin from '@/components/mixins/Translations'
 
 import logger from '@/logging'
 
 export default {
   mixins: [TranslationsMixin],
   props: {
-    title: {type: Boolean, default: true},
-    playlist: {type: Object, default: null}
-  },
-  mounted () {
-    $(this.$el).find('.dropdown').dropdown()
+    title: { type: Boolean, default: true },
+    playlist: { type: Object, default: null }
   },
   data () {
-    let d = {
+    const d = {
       errors: [],
       success: false,
       isLoading: false
@@ -80,26 +135,29 @@ export default {
       return [
         {
           value: 'me',
-          label: this.sharedLabels.fields.privacy_level.choices['me']
+          label: this.sharedLabels.fields.privacy_level.choices.me
         },
         {
           value: 'instance',
-          label: this.sharedLabels.fields.privacy_level.choices['instance']
+          label: this.sharedLabels.fields.privacy_level.choices.instance
         },
         {
           value: 'everyone',
-          label: this.sharedLabels.fields.privacy_level.choices['everyone']
+          label: this.sharedLabels.fields.privacy_level.choices.everyone
         }
       ]
     }
   },
+  mounted () {
+    $(this.$el).find('.dropdown').dropdown()
+  },
   methods: {
     submit () {
       this.isLoading = true
       this.success = false
       this.errors = []
-      let self = this
-      let payload = {
+      const self = this
+      const payload = {
         name: this.name,
         privacy_level: this.privacyLevel
       }
diff --git a/front/src/components/playlists/PlaylistModal.vue b/front/src/components/playlists/PlaylistModal.vue
index ea63954b8..509138a99 100644
--- a/front/src/components/playlists/PlaylistModal.vue
+++ b/front/src/components/playlists/PlaylistModal.vue
@@ -1,80 +1,160 @@
 <template>
-  <modal @update:show="update" :show="$store.state.playlists.showModal">
+  <modal
+    :show="$store.state.playlists.showModal"
+    @update:show="update"
+  >
     <h4 class="header">
       <template v-if="track">
         <h2 class="ui header">
-          <translate translate-context="Popup/Playlist/Title/Verb">Add to playlist</translate>
+          <translate translate-context="Popup/Playlist/Title/Verb">
+            Add to playlist
+          </translate>
           <div
+            v-translate="{artist: track.artist.name, title: track.title}"
             class="ui sub header"
             translate-context="Popup/Playlist/Paragraph"
-            v-translate="{artist: track.artist.name, title: track.title}"
-            :translate-params="{artist: track.artist.name, title: track.title}">
+            :translate-params="{artist: track.artist.name, title: track.title}"
+          >
             "%{ title }", by %{ artist }
           </div>
         </h2>
       </template>
-      <translate v-else translate-context="Popup/Playlist/Title/Verb">Manage playlists</translate>
+      <translate
+        v-else
+        translate-context="Popup/Playlist/Title/Verb"
+      >
+        Manage playlists
+      </translate>
     </h4>
     <div class="scrolling content">
-      <playlist-form :key="formKey"></playlist-form>
-      <div class="ui divider"></div>
+      <playlist-form :key="formKey" />
+      <div class="ui divider" />
       <div v-if="playlists.length > 0">
-        <div v-if="showDuplicateTrackAddConfirmation" role="alert" class="ui warning message">
-          <p translate-context="Popup/Playlist/Paragraph"
+        <div
+          v-if="showDuplicateTrackAddConfirmation"
+          role="alert"
+          class="ui warning message"
+        >
+          <p
             v-translate="{track: track.title, playlist: duplicateTrackAddInfo.playlist_name}"
-            :translate-params="{track: track.title, playlist: duplicateTrackAddInfo.playlist_name}"><strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>.</p>
+            translate-context="Popup/Playlist/Paragraph"
+            :translate-params="{track: track.title, playlist: duplicateTrackAddInfo.playlist_name}"
+          >
+            <strong>%{ track }</strong> is already in <strong>%{ playlist }</strong>.
+          </p>
           <button
+            class="ui small basic cancel button"
             @click="duplicateTrackAddConfirm(false)"
-            class="ui small basic cancel button"><translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+          >
+            <translate translate-context="*/*/Button.Label/Verb">
+              Cancel
+            </translate>
           </button>
           <button
             class="ui small success button"
-            @click="addToPlaylist(lastSelectedPlaylist, true)">
-              <translate translate-context="*/Playlist/Button.Label/Verb">Add anyways</translate></button>
+            @click="addToPlaylist(lastSelectedPlaylist, true)"
+          >
+            <translate translate-context="*/Playlist/Button.Label/Verb">
+              Add anyways
+            </translate>
+          </button>
         </div>
-        <div v-if="errors.length > 0" role="alert" class="ui negative message">
-          <h4 class="header"><translate translate-context="Popup/Playlist/Error message.Title">The track can't be added to a playlist</translate></h4>
+        <div
+          v-if="errors.length > 0"
+          role="alert"
+          class="ui negative message"
+        >
+          <h4 class="header">
+            <translate translate-context="Popup/Playlist/Error message.Title">
+              The track can't be added to a playlist
+            </translate>
+          </h4>
           <ul class="list">
-            <li v-for="error in errors">{{ error }}</li>
+            <li
+              v-for="(error, key) in errors"
+              :key="key"
+            >
+              {{ error }}
+            </li>
           </ul>
         </div>
-        <h4 class="ui header"><translate translate-context="Popup/Playlist/Title">Available playlists</translate></h4>
+        <h4 class="ui header">
+          <translate translate-context="Popup/Playlist/Title">
+            Available playlists
+          </translate>
+        </h4>
         <div class="ui form">
           <div class="fields">
             <div class="field">
               <label for="playlist-name-filter"><translate translate-context="Popup/Playlist/Label">Filter</translate></label>
-              <input id="playlist-name-filter" v-model="playlistNameFilter" type="text" class="inline" :placeholder="labels.filterPlaylistField" />
+              <input
+                id="playlist-name-filter"
+                v-model="playlistNameFilter"
+                type="text"
+                class="inline"
+                :placeholder="labels.filterPlaylistField"
+              >
             </div>
           </div>
         </div>
-        <table v-if="sortedPlaylists.length > 0" class="ui unstackable very basic table">
+        <table
+          v-if="sortedPlaylists.length > 0"
+          class="ui unstackable very basic table"
+        >
           <thead>
             <tr>
               <th><span class="visually-hidden"><translate translate-context="*/*/*/Verb">Edit</translate></span></th>
-              <th><translate translate-context="*/*/*/Noun">Name</translate></th>
-              <th class="sorted descending"><translate translate-context="Popup/Playlist/Table.Label/Short">Last modification</translate></th>
-              <th><translate translate-context="*/*/*">Tracks</translate></th>
+              <th>
+                <translate translate-context="*/*/*/Noun">
+                  Name
+                </translate>
+              </th>
+              <th class="sorted descending">
+                <translate translate-context="Popup/Playlist/Table.Label/Short">
+                  Last modification
+                </translate>
+              </th>
+              <th>
+                <translate translate-context="*/*/*">
+                  Tracks
+                </translate>
+              </th>
             </tr>
           </thead>
           <tbody>
-            <tr v-for="playlist in sortedPlaylists">
+            <tr
+              v-for="(playlist, key) in sortedPlaylists"
+              :key="key"
+            >
               <td>
                 <router-link
                   class="ui icon basic small button"
-                  :to="{name: 'library.playlists.detail', params: {id: playlist.id }, query: {mode: 'edit'}}"><i class="ui pencil icon"></i>
-                  <span class="visually-hidden"><translate translate-context="*/*/*/Verb">Edit</translate></span></router-link>
+                  :to="{name: 'library.playlists.detail', params: {id: playlist.id }, query: {mode: 'edit'}}"
+                >
+                  <i class="ui pencil icon" />
+                  <span class="visually-hidden"><translate translate-context="*/*/*/Verb">Edit</translate></span>
+                </router-link>
               </td>
               <td>
-                <router-link v-on:click.native="update(false)" :to="{name: 'library.playlists.detail', params: {id: playlist.id }}">{{ playlist.name }}</router-link></td>
-              <td><human-date :date="playlist.modification_date"></human-date></td>
+                <router-link
+                  :to="{name: 'library.playlists.detail', params: {id: playlist.id }}"
+                  @click.native="update(false)"
+                >
+                  {{ playlist.name }}
+                </router-link>
+              </td>
+              <td><human-date :date="playlist.modification_date" /></td>
               <td>{{ playlist.tracks_count }}</td>
               <td>
                 <button
                   v-if="track"
                   class="ui success icon basic small right floated button"
                   :title="labels.addToPlaylist"
-                  @click.prevent="addToPlaylist(playlist.id, false)">
-                  <i class="plus icon"></i> <translate translate-context="Popup/Playlist/Table.Button.Label/Verb">Add track</translate>
+                  @click.prevent="addToPlaylist(playlist.id, false)"
+                >
+                  <i class="plus icon" /> <translate translate-context="Popup/Playlist/Table.Button.Label/Verb">
+                    Add track
+                  </translate>
                 </button>
               </td>
             </tr>
@@ -83,35 +163,41 @@
         <template v-else>
           <div class="ui small placeholder segment component-placeholder">
             <h4 class="ui header">
-               <translate translate-context="Popup/Playlist/EmptyState">No results matching your filter</translate>
-            </h4>
-          </div>
-        </template>
-        <template v-else>
-          <div class="ui placeholder segment">
-            <div class="ui icon header">
-              <i class="list icon"></i>
-              <translate translate-context="Content/Home/Placeholder">
-                No playlists have been created yet
+              <translate translate-context="Popup/Playlist/EmptyState">
+                No results matching your filter
               </translate>
-            </div>
+            </h4>
           </div>
         </template>
       </div>
+      <template v-else>
+        <div class="ui placeholder segment">
+          <div class="ui icon header">
+            <i class="list icon" />
+            <translate translate-context="Content/Home/Placeholder">
+              No playlists have been created yet
+            </translate>
+          </div>
+        </div>
+      </template>
     </div>
     <div class="actions">
-      <button class="ui basic cancel button"><translate translate-context="*/*/Button.Label/Verb">Cancel</translate></button>
+      <button class="ui basic cancel button">
+        <translate translate-context="*/*/Button.Label/Verb">
+          Cancel
+        </translate>
+      </button>
     </div>
   </modal>
 </template>
 
 <script>
-import filter from "lodash/fp/filter";
-import sortBy from "lodash/fp/sortBy";
-import flow from "lodash/fp/flow";
+import filter from 'lodash/fp/filter'
+import sortBy from 'lodash/fp/sortBy'
+import flow from 'lodash/fp/flow'
 
 import axios from 'axios'
-import {mapState} from 'vuex'
+import { mapState } from 'vuex'
 
 import logger from '@/logging'
 import Modal from '@/components/semantic/Modal'
@@ -129,38 +215,7 @@ export default {
       playlistNameFilter: '',
       duplicateTrackAddInfo: {},
       showDuplicateTrackAddConfirmation: false,
-      lastSelectedPlaylist: -1,
-    }
-  },
-  methods: {
-    update (v) {
-      this.$store.commit('playlists/showModal', v)
-    },
-    addToPlaylist (playlistId, allowDuplicate) {
-      let self = this
-      let payload = {
-        tracks: [this.track.id],
-        allow_duplicates: allowDuplicate
-      }
-
-      self.lastSelectedPlaylist = playlistId
-
-      return axios.post(`playlists/${playlistId}/add`, payload).then(response => {
-        logger.default.info('Successfully added track to playlist')
-        self.update(false)
-        self.$store.dispatch('playlists/fetchOwn')
-      }, error => {
-        if (error.backendErrors.length == 1 && error.backendErrors[0].code == 'tracks_already_exist_in_playlist') {
-          self.duplicateTrackAddInfo = error.backendErrors[0]
-          self.showDuplicateTrackAddConfirmation = true
-        } else {
-          self.errors = error.backendErrors
-          self.showDuplicateTrackAddConfirmation = false
-        }
-      })
-    },
-    duplicateTrackAddConfirm (v) {
-      this.showDuplicateTrackAddConfirmation = v
+      lastSelectedPlaylist: -1
     }
   },
   computed: {
@@ -175,10 +230,10 @@ export default {
       }
     },
     sortedPlaylists () {
-      let regexp = new RegExp(this.playlistNameFilter, 'i');
-      let p = flow(
+      const regexp = new RegExp(this.playlistNameFilter, 'i')
+      const p = flow(
         filter((e) => e.name.match(regexp) !== null),
-        sortBy((e) => { return e.modification_date }),
+        sortBy((e) => { return e.modification_date })
       )(this.playlists)
       p.reverse()
       return p
@@ -193,6 +248,37 @@ export default {
       this.formKey = String(new Date())
       this.showDuplicateTrackAddConfirmation = false
     }
+  },
+  methods: {
+    update (v) {
+      this.$store.commit('playlists/showModal', v)
+    },
+    addToPlaylist (playlistId, allowDuplicate) {
+      const self = this
+      const payload = {
+        tracks: [this.track.id],
+        allow_duplicates: allowDuplicate
+      }
+
+      self.lastSelectedPlaylist = playlistId
+
+      return axios.post(`playlists/${playlistId}/add`, payload).then(response => {
+        logger.default.info('Successfully added track to playlist')
+        self.update(false)
+        self.$store.dispatch('playlists/fetchOwn')
+      }, error => {
+        if (error.backendErrors.length === 1 && error.backendErrors[0].code === 'tracks_already_exist_in_playlist') {
+          self.duplicateTrackAddInfo = error.backendErrors[0]
+          self.showDuplicateTrackAddConfirmation = true
+        } else {
+          self.errors = error.backendErrors
+          self.showDuplicateTrackAddConfirmation = false
+        }
+      })
+    },
+    duplicateTrackAddConfirm (v) {
+      this.showDuplicateTrackAddConfirmation = v
+    }
   }
 }
 </script>
diff --git a/front/src/components/playlists/TrackPlaylistIcon.vue b/front/src/components/playlists/TrackPlaylistIcon.vue
index aafdfe4da..fbae04d29 100644
--- a/front/src/components/playlists/TrackPlaylistIcon.vue
+++ b/front/src/components/playlists/TrackPlaylistIcon.vue
@@ -1,18 +1,22 @@
 <template>
   <button
-    @click.stop="$store.commit('playlists/chooseTrack', track)"
     v-if="button"
-    :class="['ui', 'icon', 'labeled', 'button']">
-    <i class="list icon"></i>
-    <translate translate-context="Sidebar/Player/Icon.Tooltip/Verb">Add to playlist…</translate>
+    :class="['ui', 'icon', 'labeled', 'button']"
+    @click.stop="$store.commit('playlists/chooseTrack', track)"
+  >
+    <i class="list icon" />
+    <translate translate-context="Sidebar/Player/Icon.Tooltip/Verb">
+      Add to playlist…
+    </translate>
   </button>
   <button
     v-else
-    @click.stop="$store.commit('playlists/chooseTrack', track)"
     :class="['ui', 'basic', 'circular', 'icon', {'really': !border}, 'button']"
     :aria-label="labels.addToPlaylist"
-    :title="labels.addToPlaylist">
-    <i :class="['list', 'basic', 'icon']"></i>
+    :title="labels.addToPlaylist"
+    @click.stop="$store.commit('playlists/chooseTrack', track)"
+  >
+    <i :class="['list', 'basic', 'icon']" />
   </button>
 </template>
 
@@ -20,9 +24,9 @@
 
 export default {
   props: {
-    track: {type: Object},
-    button: {type: Boolean, default: false},
-    border: {type: Boolean, default: false},
+    track: { type: Object, default: function () { return {} } },
+    button: { type: Boolean, default: false },
+    border: { type: Boolean, default: false }
   },
   data () {
     return {
diff --git a/front/src/components/playlists/Widget.vue b/front/src/components/playlists/Widget.vue
index d3bdf8f13..686c651ea 100644
--- a/front/src/components/playlists/Widget.vue
+++ b/front/src/components/playlists/Widget.vue
@@ -1,36 +1,58 @@
 <template>
   <div>
-    <h3 v-if="!!this.$slots.title" class="ui header">
-      <slot name="title"></slot>
+    <h3
+      v-if="!!$slots.title"
+      class="ui header"
+    >
+      <slot name="title" />
     </h3>
-    <div v-if="isLoading" class="ui inverted active dimmer">
-      <div class="ui loader"></div>
+    <div
+      v-if="isLoading"
+      class="ui inverted active dimmer"
+    >
+      <div class="ui loader" />
     </div>
-    <div v-if="playlistsExist" class="ui cards app-cards">
-      <playlist-card v-for="playlist in objects" :key="playlist.id" :playlist="playlist"></playlist-card>
+    <div
+      v-if="playlistsExist"
+      class="ui cards app-cards"
+    >
+      <playlist-card
+        v-for="playlist in objects"
+        :key="playlist.id"
+        :playlist="playlist"
+      />
     </div>
-    <div v-else class="ui placeholder segment">
+    <div
+      v-else
+      class="ui placeholder segment"
+    >
       <div class="ui icon header">
-        <i class="list icon"></i>
+        <i class="list icon" />
         <translate translate-context="Content/Home/Placeholder">
           No playlists have been created yet
         </translate>
       </div>
       <button
         v-if="$store.state.auth.authenticated"
-        @click="$store.commit('playlists/chooseTrack', null)"
         class="ui success icon labeled button"
-        >
-        <i class="list icon"></i>
+        @click="$store.commit('playlists/chooseTrack', null)"
+      >
+        <i class="list icon" />
         <translate translate-context="Content/Home/CreatePlaylist">
           Create Playlist
         </translate>
       </button>
     </div>
     <template v-if="nextPage">
-      <div class="ui hidden divider"></div>
-      <button v-if="nextPage" @click="fetchData(nextPage)" :class="['ui', 'basic', 'button']">
-        <translate translate-context="*/*/Button,Label">Show more</translate>
+      <div class="ui hidden divider" />
+      <button
+        v-if="nextPage"
+        :class="['ui', 'basic', 'button']"
+        @click="fetchData(nextPage)"
+      >
+        <translate translate-context="*/*/Button,Label">
+          Show more
+        </translate>
       </button>
     </template>
   </div>
@@ -42,13 +64,13 @@ import axios from 'axios'
 import PlaylistCard from '@/components/playlists/Card'
 
 export default {
-  props: {
-    filters: {type: Object, required: true},
-    url: {type: String, required: true}
-  },
   components: {
     PlaylistCard
   },
+  props: {
+    filters: { type: Object, required: true },
+    url: { type: String, required: true }
+  },
   data () {
     return {
       objects: [],
@@ -59,25 +81,33 @@ export default {
       nextPage: null
     }
   },
-  created () {
-    this.fetchData(this.url)
-  },
   computed: {
     playlistsExist: function () {
       return this.objects.length > 0
     }
   },
+  watch: {
+    offset () {
+      this.fetchData()
+    },
+    '$store.state.moderation.lastUpdate': function () {
+      this.fetchData(this.url)
+    }
+  },
+  created () {
+    this.fetchData(this.url)
+  },
   methods: {
     fetchData (url) {
       if (!url) {
         return
       }
       this.isLoading = true
-      let self = this
-      let params = _.clone(this.filters)
+      const self = this
+      const params = _.clone(this.filters)
       params.page_size = this.limit
       params.offset = this.offset
-      axios.get(url, {params: params}).then((response) => {
+      axios.get(url, { params: params }).then((response) => {
         self.previousPage = response.data.previous
         self.nextPage = response.data.next
         self.isLoading = false
@@ -94,14 +124,6 @@ export default {
         this.offset = Math.max(this.offset - this.limit, 0)
       }
     }
-  },
-  watch: {
-    offset () {
-      this.fetchData()
-    },
-    "$store.state.moderation.lastUpdate": function () {
-      this.fetchData(this.url)
-    }
   }
 }
 </script>
diff --git a/front/src/components/radios/Button.vue b/front/src/components/radios/Button.vue
index 2e7bbec08..8c16795e8 100644
--- a/front/src/components/radios/Button.vue
+++ b/front/src/components/radios/Button.vue
@@ -1,8 +1,22 @@
 <template>
-  <button @click="toggleRadio" :class="['ui', 'primary', {'inverted': running}, 'icon', 'labeled', 'button']">
-    <i class="ui feed icon" role="button"></i>
-    <template v-if="running"><translate translate-context="*/Player/Button.Label/Short, Verb">Stop radio</translate></template>
-    <template v-else><translate translate-context="*/Queue/Button.Label/Short, Verb">Play radio</translate></template>
+  <button
+    :class="['ui', 'primary', {'inverted': running}, 'icon', 'labeled', 'button']"
+    @click="toggleRadio"
+  >
+    <i
+      class="ui feed icon"
+      role="button"
+    />
+    <template v-if="running">
+      <translate translate-context="*/Player/Button.Label/Short, Verb">
+        Stop radio
+      </translate>
+    </template>
+    <template v-else>
+      <translate translate-context="*/Queue/Button.Label/Short, Verb">
+        Play radio
+      </translate>
+    </template>
   </button>
 </template>
 
@@ -11,10 +25,21 @@
 import lodash from '@/lodash'
 export default {
   props: {
-    customRadioId: {required: false},
-    type: {type: String, required: false},
-    clientOnly: {type: Boolean, default: false},
-    objectId: {default: null}
+    customRadioId: { type: Number, default: 0, required: false },
+    type: { type: String, required: false, default: '' },
+    clientOnly: { type: Boolean, default: false },
+    objectId: { type: Number, default: null }
+  },
+  computed: {
+    running () {
+      const state = this.$store.state.radios
+      const current = state.current
+      if (!state.running) {
+        return false
+      } else {
+        return current.type === this.type && lodash.isEqual(current.objectId, this.objectId) && current.customRadioId === this.customRadioId
+      }
+    }
   },
   methods: {
     toggleRadio () {
@@ -25,21 +50,10 @@ export default {
           type: this.type,
           objectId: this.objectId,
           customRadioId: this.customRadioId,
-          clientOnly: this.clientOnly,
+          clientOnly: this.clientOnly
         })
       }
     }
-  },
-  computed: {
-    running () {
-      let state = this.$store.state.radios
-      let current = state.current
-      if (!state.running) {
-        return false
-      } else {
-        return current.type === this.type && lodash.isEqual(current.objectId, this.objectId) && current.customRadioId === this.customRadioId
-      }
-    }
   }
 }
 </script>
diff --git a/front/src/components/radios/Card.vue b/front/src/components/radios/Card.vue
index 47bc0fe1a..3b7f98a19 100644
--- a/front/src/components/radios/Card.vue
+++ b/front/src/components/radios/Card.vue
@@ -1,44 +1,60 @@
 <template>
-    <div class="ui card">
-      <div class="content">
-        <h4 class="header">
-          <router-link v-if="radio.id" class="discrete link" :to="{name: 'library.radios.detail', params: {id: radio.id}}">
-            {{ radio.name }}
-          </router-link>
-          <template v-else>
-            {{ radio.name }}
-          </template>
-        </h4>
-        <div class="description">
-          {{ radio.description }}
-        </div>
-      </div>
-      <div class="extra content">
-        <user-link v-if="radio.user" :user="radio.user" class="left floated" />
-        <div class="ui hidden divider"></div>
-        <radio-button class="right floated button" :type="type" :custom-radio-id="customRadioId" :object-id="objectId"></radio-button>
+  <div class="ui card">
+    <div class="content">
+      <h4 class="header">
         <router-link
-          class="ui success 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 translate-context="Content/*/Button.Label/Verb">Edit</translate>
+          v-if="radio.id"
+          class="discrete link"
+          :to="{name: 'library.radios.detail', params: {id: radio.id}}"
+        >
+          {{ radio.name }}
         </router-link>
+        <template v-else>
+          {{ radio.name }}
+        </template>
+      </h4>
+      <div class="description">
+        {{ radio.description }}
       </div>
     </div>
+    <div class="extra content">
+      <user-link
+        v-if="radio.user"
+        :user="radio.user"
+        class="left floated"
+      />
+      <div class="ui hidden divider" />
+      <radio-button
+        class="right floated button"
+        :type="type"
+        :custom-radio-id="customRadioId"
+        :object-id="objectId"
+      />
+      <router-link
+        v-if="$store.state.auth.authenticated && type === 'custom' && radio.user.id === $store.state.auth.profile.id"
+        class="ui success button right floated"
+        :to="{name: 'library.radios.edit', params: {id: customRadioId }}"
+      >
+        <translate translate-context="Content/*/Button.Label/Verb">
+          Edit
+        </translate>
+      </router-link>
+    </div>
+  </div>
 </template>
 
 <script>
 import RadioButton from './Button'
 
 export default {
-  props: {
-    type: {type: String, required: true},
-    customRadio: {required: false},
-    objectId: {required: false},
-  },
   components: {
     RadioButton
   },
+  props: {
+    type: { type: String, required: true, default: '' },
+    customRadio: { type: Boolean, required: false },
+    objectId: { type: Number, required: false, default: 0 }
+  },
   computed: {
     radio () {
       if (this.customRadio) {
diff --git a/front/src/components/tags/List.vue b/front/src/components/tags/List.vue
index 0da76378e..e9f695fca 100644
--- a/front/src/components/tags/List.vue
+++ b/front/src/components/tags/List.vue
@@ -1,30 +1,43 @@
 <template>
   <div class="component-tags-list">
     <router-link
+      v-for="tag in toDisplay"
+      :key="tag"
       :to="{name: detailRoute, params: {id: tag}}"
       :class="['ui', 'circular', 'hashtag', 'label', labelClasses]"
-      v-for="tag in toDisplay"
-      :key="tag">
+    >
       #{{ tag|truncate(truncateSize) }}
     </router-link>
-    <div role="button" @click.prevent="honorLimit = false" class="ui circular inverted accent label" v-if="showMore && toDisplay.length < tags.length">
-      <translate translate-context="Content/*/Button/Label/Verb" :translate-params="{count: tags.length - toDisplay.length}" :translate-n="tags.length - toDisplay.length" translate-plural="Show %{ count } more tags">Show 1 more tag</translate>
+    <div
+      v-if="showMore && toDisplay.length < tags.length"
+      role="button"
+      class="ui circular inverted accent label"
+      @click.prevent="honorLimit = false"
+    >
+      <translate
+        translate-context="Content/*/Button/Label/Verb"
+        :translate-params="{count: tags.length - toDisplay.length}"
+        :translate-n="tags.length - toDisplay.length"
+        translate-plural="Show %{ count } more tags"
+      >
+        Show 1 more tag
+      </translate>
     </div>
   </div>
 </template>
 <script>
 export default {
   props: {
-    tags: {type: Array, required: true},
-    showMore: {type: Boolean, default: true},
-    truncateSize: {type: Number, default: 25},
-    limit: {type: Number, default: 5},
-    labelClasses: {type: String, default: ''},
-    detailRoute: {type: String, default: 'library.tags.detail'},
+    tags: { type: Array, required: true },
+    showMore: { type: Boolean, default: true },
+    truncateSize: { type: Number, default: 25 },
+    limit: { type: Number, default: 5 },
+    labelClasses: { type: String, default: '' },
+    detailRoute: { type: String, default: 'library.tags.detail' }
   },
   data () {
     return {
-      honorLimit: true,
+      honorLimit: true
     }
   },
   computed: {
diff --git a/front/src/components/utils/global-events.vue b/front/src/components/utils/global-events.vue
index dd25865c9..6cc7f8472 100644
--- a/front/src/components/utils/global-events.vue
+++ b/front/src/components/utils/global-events.vue
@@ -18,13 +18,12 @@ function extractEventOptions (eventDescriptor) {
 }
 
 export default {
-  render: h => h(),
 
   mounted () {
     this._listeners = Object.create(null)
     Object.keys(this.$listeners).forEach(event => {
       const handler = this.$listeners[event]
-      let wrapper = function (event) {
+      const wrapper = function (event) {
         // we check here the event is not triggered from an input
         // to avoid collisions
         if (!$(event.target).is('.field, :input, [contenteditable]')) {
@@ -47,6 +46,7 @@ export default {
         this._listeners[event]
       )
     }
-  }
+  },
+  render: h => h()
 }
 </script>
diff --git a/front/src/edits.js b/front/src/edits.js
index baa9bbb32..637f28e46 100644
--- a/front/src/edits.js
+++ b/front/src/edits.js
@@ -16,7 +16,7 @@ export default {
       type: 'content',
       required: true,
       label: this.$pgettext('*/*/*/Noun', 'Description'),
-      getValue: (obj) => { return obj.description || {text: null, content_type: 'text/markdown'}},
+      getValue: (obj) => { return obj.description || { text: null, content_type: 'text/markdown' } },
       getValueRepr: getContentValueRepr
     }
     const cover = {
@@ -51,7 +51,7 @@ export default {
             label: this.$pgettext('*/*/*/Noun', 'Tags'),
             getValue: (obj) => { return obj.tags },
             getValueRepr: getTagsValueRepr
-          },
+          }
         ]
       },
       album: {
@@ -113,7 +113,7 @@ export default {
             type: 'license',
             required: false,
             label: this.$pgettext('Content/*/*/Noun', 'License'),
-            getValue: (obj) => { return obj.license },
+            getValue: (obj) => { return obj.license }
           },
           {
             id: 'tags',
@@ -132,23 +132,23 @@ export default {
     return this.configs[this.objectType]
   },
   getFieldConfig (configs, type, fieldId) {
-    let c = configs[type]
+    const c = configs[type]
     return c.fields.filter((f) => {
-      return f.id == fieldId
+      return f.id === fieldId
     })[0]
   },
   getCurrentState () {
-    let self = this
-    let s = {}
+    const self = this
+    const s = {}
     this.config.fields.forEach(f => {
-      s[f.id] = {value: f.getValue(self.object)}
+      s[f.id] = { value: f.getValue(self.object) }
     })
     return s
   },
   getCurrentStateForObj (obj, config) {
-    let s = {}
+    const s = {}
     config.fields.forEach(f => {
-      s[f.id] = {value: f.getValue(obj)}
+      s[f.id] = { value: f.getValue(obj) }
     })
     return s
   },
@@ -161,8 +161,8 @@ export default {
       return false
     }
     return (
-      this.obj.created_by.full_username === this.$store.state.auth.fullUsername
-      || this.$store.state.auth.availablePermissions['library']
+      this.obj.created_by.full_username === this.$store.state.auth.fullUsername ||
+      this.$store.state.auth.availablePermissions.library
     )
   },
   getCanApprove () {
@@ -172,20 +172,20 @@ export default {
     if (!this.$store.state.auth.authenticated) {
       return false
     }
-    return this.$store.state.auth.availablePermissions['library']
+    return this.$store.state.auth.availablePermissions.library
   },
   getCanEdit () {
     if (!this.$store.state.auth.authenticated) {
       return false
     }
 
-    let libraryPermission = this.$store.state.auth.availablePermissions['library']
-    let objData = this.object || {}
+    const libraryPermission = this.$store.state.auth.availablePermissions.library
+    const objData = this.object || {}
     let isOwner = false
     if (objData.attributed_to) {
       isOwner = this.$store.state.auth.fullUsername === objData.attributed_to.full_username
     }
     return libraryPermission || isOwner
-  },
+  }
 
 }
diff --git a/front/src/embed.js b/front/src/embed.js
index a079ee085..dc3e866f3 100644
--- a/front/src/embed.js
+++ b/front/src/embed.js
@@ -10,8 +10,8 @@ Vue.config.productionTip = false
 /* eslint-disable no-new */
 new Vue({
   el: '#app',
+  components: { EmbedFrame },
   render (h) {
     return h('EmbedFrame')
-  },
-  components: { EmbedFrame }
+  }
 })
diff --git a/front/src/entities.js b/front/src/entities.js
index c13d7c6e2..3bafa357b 100644
--- a/front/src/entities.js
+++ b/front/src/entities.js
@@ -15,8 +15,8 @@ export default {
           return `manage/library/artists/${obj.id}/`
         },
         urls: {
-          getDetail: (obj) => { return {name: 'library.artists.detail', params: {id: obj.id}}},
-          getAdminDetail: (obj) => { return {name: 'manage.library.artists.detail', params: {id: obj.id}}},
+          getDetail: (obj) => { return { name: 'library.artists.detail', params: { id: obj.id } } },
+          getAdminDetail: (obj) => { return { name: 'manage.library.artists.detail', params: { id: obj.id } } }
         },
         moderatedFields: [
           {
@@ -40,7 +40,7 @@ export default {
             id: 'mbid',
             label: this.$pgettext('*/*/*/Noun', 'MusicBrainz ID'),
             getValue: (obj) => { return obj.mbid }
-          },
+          }
         ]
       },
       album: {
@@ -50,8 +50,8 @@ export default {
           return `manage/library/albums/${obj.id}/`
         },
         urls: {
-          getDetail: (obj) => { return {name: 'library.albums.detail', params: {id: obj.id}}},
-          getAdminDetail: (obj) => { return {name: 'manage.library.albums.detail', params: {id: obj.id}}}
+          getDetail: (obj) => { return { name: 'library.albums.detail', params: { id: obj.id } } },
+          getAdminDetail: (obj) => { return { name: 'manage.library.albums.detail', params: { id: obj.id } } }
         },
         moderatedFields: [
           {
@@ -81,7 +81,7 @@ export default {
             id: 'mbid',
             label: this.$pgettext('*/*/*/Noun', 'MusicBrainz ID'),
             getValue: (obj) => { return obj.mbid }
-          },
+          }
         ]
       },
       track: {
@@ -91,8 +91,8 @@ export default {
           return `manage/library/tracks/${obj.id}/`
         },
         urls: {
-          getDetail: (obj) => { return {name: 'library.tracks.detail', params: {id: obj.id}}},
-          getAdminDetail: (obj) => { return {name: 'manage.library.tracks.detail', params: {id: obj.id}}}
+          getDetail: (obj) => { return { name: 'library.tracks.detail', params: { id: obj.id } } },
+          getAdminDetail: (obj) => { return { name: 'manage.library.tracks.detail', params: { id: obj.id } } }
         },
         moderatedFields: [
           {
@@ -113,7 +113,7 @@ export default {
           {
             id: 'license',
             label: this.$pgettext('Content/*/*/Noun', 'License'),
-            getValue: (obj) => { return obj.license },
+            getValue: (obj) => { return obj.license }
           },
           {
             id: 'tags',
@@ -125,7 +125,7 @@ export default {
             id: 'mbid',
             label: this.$pgettext('*/*/*/Noun', 'MusicBrainz ID'),
             getValue: (obj) => { return obj.mbid }
-          },
+          }
         ]
       },
       library: {
@@ -135,7 +135,7 @@ export default {
           return `manage/library/libraries/${obj.uuid}/`
         },
         urls: {
-          getAdminDetail: (obj) => { return {name: 'manage.library.libraries.detail', params: {id: obj.uuid}}}
+          getAdminDetail: (obj) => { return { name: 'manage.library.libraries.detail', params: { id: obj.uuid } } }
         },
         moderatedFields: [
           {
@@ -152,14 +152,14 @@ export default {
             id: 'privacy_level',
             label: this.$pgettext('*/*/*', 'Visibility'),
             getValue: (obj) => { return obj.privacy_level }
-          },
+          }
         ]
       },
       playlist: {
         label: this.$pgettext('*/*/*', 'Playlist'),
         icon: 'list',
         urls: {
-          getDetail: (obj) => { return {name: 'library.playlists.detail', params: {id: obj.id}}},
+          getDetail: (obj) => { return { name: 'library.playlists.detail', params: { id: obj.id } } }
           // getAdminDetail: (obj) => { return {name: 'manage.playlists.detail', params: {id: obj.id}}}
         },
         moderatedFields: [
@@ -172,15 +172,15 @@ export default {
             id: 'privacy_level',
             label: this.$pgettext('*/*/*', 'Visibility'),
             getValue: (obj) => { return obj.privacy_level }
-          },
+          }
         ]
       },
       account: {
         label: this.$pgettext('*/*/*/Noun', 'Account'),
         icon: 'user',
         urls: {
-          getDetail: (obj) => { return {name: 'profile.full.overview', params: {username: obj.preferred_username, domain: obj.domain}}},
-          getAdminDetail: (obj) => { return {name: 'manage.moderation.accounts.detail', params: {id: `${obj.preferred_username}@${obj.domain}`}}}
+          getDetail: (obj) => { return { name: 'profile.full.overview', params: { username: obj.preferred_username, domain: obj.domain } } },
+          getAdminDetail: (obj) => { return { name: 'manage.moderation.accounts.detail', params: { id: `${obj.preferred_username}@${obj.domain}` } } }
         },
         moderatedFields: [
           {
@@ -192,15 +192,15 @@ export default {
             id: 'summary',
             label: this.$pgettext('*/*/*/Noun', 'Bio'),
             getValue: (obj) => { return obj.summary }
-          },
+          }
         ]
       },
       channel: {
         label: this.$pgettext('*/*/*', 'Channel'),
         icon: 'stream',
         urls: {
-          getDetail: (obj) => { return {name: 'channels.detail', params: {id: obj.uuid}}},
-          getAdminDetail: (obj) => { return {name: 'manage.channels.detail', params: {id: obj.uuid}}}
+          getDetail: (obj) => { return { name: 'channels.detail', params: { id: obj.uuid } } },
+          getAdminDetail: (obj) => { return { name: 'manage.channels.detail', params: { id: obj.uuid } } }
         },
         moderatedFields: [
           {
@@ -219,9 +219,9 @@ export default {
             label: this.$pgettext('*/*/*/Noun', 'Tags'),
             getValue: (obj) => { return obj.tags },
             getValueRepr: getTagsValueRepr
-          },
+          }
         ]
-      },
+      }
     }
   },
 
@@ -229,17 +229,17 @@ export default {
     return this.configs[this.objectType]
   },
   getFieldConfig (configs, type, fieldId) {
-    let c = configs[type]
+    const c = configs[type]
     return c.fields.filter((f) => {
-      return f.id == fieldId
+      return f.id === fieldId
     })[0]
   },
   getCurrentStateForObj (obj, config) {
-    let s = {}
+    const s = {}
     config.fields.forEach(f => {
-      s[f.id] = {value: f.getValue(obj)}
+      s[f.id] = { value: f.getValue(obj) }
     })
     return s
-  },
+  }
 
 }
diff --git a/front/src/filters.js b/front/src/filters.js
index 030e50e83..75834ffbd 100644
--- a/front/src/filters.js
+++ b/front/src/filters.js
@@ -14,14 +14,14 @@ export function truncate (str, max, ellipsis, middle) {
     return str
   }
   if (middle) {
-    var sepLen = 1,
-        charsToShow = max - sepLen,
-        frontChars = Math.ceil(charsToShow/2),
-        backChars = Math.floor(charsToShow/2);
+    const sepLen = 1
+    const charsToShow = max - sepLen
+    const frontChars = Math.ceil(charsToShow / 2)
+    const backChars = Math.floor(charsToShow / 2)
 
     return str.substr(0, frontChars) +
            ellipsis +
-           str.substr(str.length - backChars);
+           str.substr(str.length - backChars)
   } else {
     return str.slice(0, max) + ellipsis
   }
@@ -51,20 +51,20 @@ export function fromNow (date, locale) {
     relativeTime: {
       future: 'in %s',
       past: '%s ago',
-      s:  'seconds',
+      s: 'seconds',
       ss: '%ss',
-      m:  'a minute',
+      m: 'a minute',
       mm: '%dm',
-      h:  'an hour',
+      h: 'an hour',
       hh: '%dh',
-      d:  'a day',
+      d: 'a day',
       dd: '%dd',
-      M:  'a month',
+      M: 'a month',
       MM: '%dM',
-      y:  'a year',
+      y: 'a year',
       yy: '%dY'
     }
-  });
+  })
   const m = moment(date)
   m.locale(locale)
   return m.fromNow(true)
@@ -73,7 +73,7 @@ export function fromNow (date, locale) {
 Vue.filter('fromNow', fromNow)
 
 export function secondsToObject (seconds) {
-  let m = moment.duration(seconds, 'seconds')
+  const m = moment.duration(seconds, 'seconds')
   return {
     seconds: m.seconds(),
     minutes: m.minutes(),
@@ -84,9 +84,9 @@ export function secondsToObject (seconds) {
 Vue.filter('secondsToObject', secondsToObject)
 
 export function padDuration (duration) {
-  var s = String(duration);
-  while (s.length < 2) {s = "0" + s;}
-  return s;
+  let s = String(duration)
+  while (s.length < 2) { s = '0' + s }
+  return s
 }
 
 Vue.filter('padDuration', padDuration)
@@ -117,15 +117,15 @@ export function capitalize (str) {
 Vue.filter('capitalize', capitalize)
 
 export function humanSize (bytes) {
-  let si = true
-  var thresh = si ? 1000 : 1024
+  const si = true
+  const thresh = si ? 1000 : 1024
   if (Math.abs(bytes) < thresh) {
     return bytes + ' B'
   }
-  var units = si
+  const units = si
     ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
     : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
-  var u = -1
+  let u = -1
   do {
     bytes /= thresh
     ++u
diff --git a/front/src/lodash.js b/front/src/lodash.js
index 91bebd2e8..7204beee4 100644
--- a/front/src/lodash.js
+++ b/front/src/lodash.js
@@ -19,5 +19,5 @@ export default {
   sum: require('lodash/sum'),
   startCase: require('lodash/startCase'),
   tap: require('lodash/tap'),
-  trim: require('lodash/trim'),
+  trim: require('lodash/trim')
 }
diff --git a/front/src/main.js b/front/src/main.js
index 79505a707..158dbe67b 100644
--- a/front/src/main.js
+++ b/front/src/main.js
@@ -1,10 +1,7 @@
 // The Vue build version to load with the `import` command
 // (runtime-only or standalone) has been set in webpack.base.conf with an alias.
 import logger from '@/logging'
-
-logger.default.info('Loading environment:', process.env.NODE_ENV)
-logger.default.debug('Environment variables:', process.env)
-import jQuery from "jquery"
+import jQuery from 'jquery'
 
 import Vue from 'vue'
 import moment from 'moment'
@@ -16,21 +13,24 @@ import store from './store'
 import GetTextPlugin from 'vue-gettext'
 import { sync } from 'vuex-router-sync'
 import locales from '@/locales'
-import createAuthRefreshInterceptor from 'axios-auth-refresh';
+import createAuthRefreshInterceptor from 'axios-auth-refresh'
 
 import filters from '@/filters' // eslint-disable-line
-import {parseAPIErrors} from '@/utils'
+import { parseAPIErrors } from '@/utils'
 import globals from '@/components/globals' // eslint-disable-line
 import './registerServiceWorker'
 
+logger.default.info('Loading environment:', process.env.NODE_ENV)
+logger.default.debug('Environment variables:', process.env)
+
 sync(store, router)
 
 window.$ = window.jQuery = require('jquery')
 require('./semantic.js')
 let APP = null
 
-let availableLanguages = (function () {
-  let l = {}
+const availableLanguages = (function () {
+  const l = {}
   locales.locales.forEach(c => {
     l[c.code] = c.label
   })
@@ -69,7 +69,7 @@ Vue.directive('dropdown', function (el, binding) {
     action: function (text, value, $el) {
       // used to ensure focusing the dropdown and clicking via keyboard
       // works as expected
-      let button = $el[0]
+      const button = $el[0]
       button.click()
       jQuery(el).find('.ui.dropdown').dropdown('hide')
     },
@@ -79,10 +79,9 @@ Vue.directive('dropdown', function (el, binding) {
 axios.defaults.xsrfCookieName = 'csrftoken'
 axios.defaults.xsrfHeaderName = 'X-CSRFToken'
 axios.interceptors.request.use(function (config) {
-
   // Do something before request is sent
   if (store.state.auth.oauth.accessToken) {
-    config.headers['Authorization'] = store.getters['auth/header']
+    config.headers.Authorization = store.getters['auth/header']
   }
   return config
 }, function (error) {
@@ -98,7 +97,7 @@ axios.interceptors.response.use(function (response) {
   if (store.state.auth.authenticated && !store.state.auth.oauth.accessToken && error.response.status === 401) {
     store.commit('auth/authenticated', false)
     logger.default.warn('Received 401 response from API, redirecting to login form', router.currentRoute.fullPath)
-    router.push({name: 'login', query: {next: router.currentRoute.fullPath}})
+    router.push({ name: 'login', query: { next: router.currentRoute.fullPath } })
   }
   if (error.response.status === 404) {
     error.backendErrors.push('Resource not found')
@@ -106,28 +105,28 @@ axios.interceptors.response.use(function (response) {
     error.backendErrors.push('Permission denied')
   } else if (error.response.status === 429) {
     let message
-    let rateLimitStatus = {
+    const rateLimitStatus = {
       limit: error.response.headers['x-ratelimit-limit'],
       scope: error.response.headers['x-ratelimit-scope'],
       remaining: error.response.headers['x-ratelimit-remaining'],
       duration: error.response.headers['x-ratelimit-duration'],
       availableSeconds: error.response.headers['retry-after'],
       reset: error.response.headers['x-ratelimit-reset'],
-      resetSeconds: error.response.headers['x-ratelimit-resetseconds'],
+      resetSeconds: error.response.headers['x-ratelimit-resetseconds']
     }
     if (rateLimitStatus.availableSeconds) {
       rateLimitStatus.availableSeconds = parseInt(rateLimitStatus.availableSeconds)
-      let tryAgain = moment().add(rateLimitStatus.availableSeconds, 's').toNow(true)
+      const tryAgain = moment().add(rateLimitStatus.availableSeconds, 's').toNow(true)
       message = APP.$pgettext('*/Error/Paragraph', 'You sent too many requests and have been rate limited, please try again in %{ delay }')
-      message = APP.$gettextInterpolate(message, {delay: tryAgain})
+      message = APP.$gettextInterpolate(message, { delay: tryAgain })
     } else {
-      message =  APP.$pgettext('*/Error/Paragraph', 'You sent too many requests and have been rate limited, please try again later')
+      message = APP.$pgettext('*/Error/Paragraph', 'You sent too many requests and have been rate limited, please try again later')
     }
     error.backendErrors.push(message)
-    store.commit("ui/addMessage", {
+    store.commit('ui/addMessage', {
       content: message,
       date: new Date(),
-      class: 'error',
+      class: 'error'
     })
     logger.default.error('This client is rate-limited!', rateLimitStatus)
   } else if (error.response.status === 500) {
@@ -137,7 +136,7 @@ axios.interceptors.response.use(function (response) {
       error.backendErrors.push(error.response.data.detail)
     } else {
       error.rawPayload = error.response.data
-      let parsedErrors = parseAPIErrors(error.response.data)
+      const parsedErrors = parseAPIErrors(error.response.data)
       error.backendErrors = [...error.backendErrors, ...parsedErrors]
     }
   }
@@ -153,15 +152,15 @@ const refreshAuth = (failedRequest) => {
     console.log('Failed request, refreshing auth…')
     // maybe the token was expired, let's try to refresh it
     return store.dispatch('auth/refreshOauthToken').then(() => {
-      failedRequest.response.config.headers['Authorization'] = store.getters["auth/header"];
-      return Promise.resolve();
+      failedRequest.response.config.headers.Authorization = store.getters['auth/header']
+      return Promise.resolve()
     })
   } else {
-    return Promise.resolve();
+    return Promise.resolve()
   }
 }
 
-createAuthRefreshInterceptor(axios, refreshAuth);
+createAuthRefreshInterceptor(axios, refreshAuth)
 
 store.dispatch('instance/fetchFrontSettings').finally(() => {
   /* eslint-disable no-new */
@@ -169,26 +168,26 @@ store.dispatch('instance/fetchFrontSettings').finally(() => {
     el: '#app',
     router,
     store,
-    render (h) {
-      return h('App')
-    },
     components: { App },
     created () {
       APP = this
       window.addEventListener('resize', this.handleResize)
-      this.handleResize();
+      this.handleResize()
     },
-    destroyed() {
+    destroyed () {
       window.removeEventListener('resize', this.handleResize)
     },
     methods: {
-      handleResize() {
+      handleResize () {
         this.$store.commit('ui/window', {
           width: window.innerWidth,
-          height: window.innerHeight,
+          height: window.innerHeight
         })
       }
     },
+    render (h) {
+      return h('App')
+    }
   })
 
   logger.default.info('Everything loaded!')
diff --git a/front/src/radios.js b/front/src/radios.js
index 3c46b711b..5650881fa 100644
--- a/front/src/radios.js
+++ b/front/src/radios.js
@@ -1,4 +1,4 @@
-import axios from "axios"
+import axios from 'axios'
 import logger from '@/logging'
 
 // import axios from 'axios'
@@ -8,19 +8,19 @@ const RADIOS = {
   // method by hand
   account: {
     offset: 1,
-    populateQueue({current, dispatch, playNow}) {
-      let params = {scope: `actor:${current.objectId.fullUsername}`, ordering: '-creation_date', page_size: 1, page: this.offset}
-      axios.get('history/listenings', {params}).then((response) => {
-        let latest = response.data.results[0]
+    populateQueue ({ current, dispatch, playNow }) {
+      const params = { scope: `actor:${current.objectId.fullUsername}`, ordering: '-creation_date', page_size: 1, page: this.offset }
+      axios.get('history/listenings', { params }).then((response) => {
+        const latest = response.data.results[0]
         if (!latest) {
           logger.default.error('No more tracks')
           dispatch('stop')
         }
         this.offset += 1
-        let append = dispatch('queue/append', {track: latest.track}, {root: true})
+        const append = dispatch('queue/append', { track: latest.track }, { root: true })
         if (playNow) {
           append.then(() => {
-            dispatch('queue/last', null, {root: true})
+            dispatch('queue/last', null, { root: true })
           })
         }
       }, (error) => {
@@ -36,7 +36,7 @@ const RADIOS = {
       if (event.actor.local_id === current.objectId.username) {
         axios.get(`tracks/${event.object.local_id}`).then((response) => {
           if (response.data.uploads.length > 0) {
-            store.dispatch('queue/append', {track: response.data})
+            store.dispatch('queue/append', { track: response.data })
             this.offset += 1
           }
         }, (error) => {
@@ -46,6 +46,6 @@ const RADIOS = {
     }
   }
 }
-export function getClientOnlyRadio({type}) {
+export function getClientOnlyRadio ({ type }) {
   return RADIOS[type]
 }
diff --git a/front/src/registerServiceWorker.js b/front/src/registerServiceWorker.js
index 0fdf6c933..aa5d66df7 100644
--- a/front/src/registerServiceWorker.js
+++ b/front/src/registerServiceWorker.js
@@ -15,15 +15,15 @@ if (process.env.NODE_ENV === 'production') {
     registered (registration) {
       console.log('Service worker has been registered.')
       // check for updates every 2 hours
-      var checkInterval = 1000 * 60 * 60 * 2
+      const checkInterval = 1000 * 60 * 60 * 2
       // var checkInterval = 1000 * 5
       setInterval(() => {
         console.log('Checking for service worker update…')
-        registration.update();
-      }, checkInterval);
-      store.commit('ui/serviceWorker', {registration: registration})
+        registration.update()
+      }, checkInterval)
+      store.commit('ui/serviceWorker', { registration: registration })
       if (registration.active) {
-        registration.active.postMessage({command: 'serverChosen', serverUrl: store.state.instance.instanceUrl})
+        registration.active.postMessage({ command: 'serverChosen', serverUrl: store.state.instance.instanceUrl })
       }
     },
     cached () {
@@ -34,7 +34,7 @@ if (process.env.NODE_ENV === 'production') {
     },
     updated (registration) {
       console.log('New content is available; please refresh!')
-      store.commit('ui/serviceWorker', {updateAvailable: true, registration: registration})
+      store.commit('ui/serviceWorker', { updateAvailable: true, registration: registration })
     },
     offline () {
       console.log('No internet connection found. App is running in offline mode.')
diff --git a/front/src/sanitize.js b/front/src/sanitize.js
index d2a8d0809..f43a69875 100644
--- a/front/src/sanitize.js
+++ b/front/src/sanitize.js
@@ -1,43 +1,43 @@
-import sanitizeHtml from "sanitize-html"
+import sanitizeHtml from 'sanitize-html'
 
 const allowedTags = [
-  "h3",
-  "h4",
-  "h5",
-  "h6",
-  "blockquote",
-  "p",
-  "a",
-  "ul",
-  "ol",
-  "nl",
-  "li",
-  "b",
-  "i",
-  "strong",
-  "em",
-  "strike",
-  "code",
-  "hr",
-  "br",
-  "div",
-  "table",
-  "thead",
-  "caption",
-  "tbody",
-  "tr",
-  "th",
-  "td",
-  "pre",
+  'h3',
+  'h4',
+  'h5',
+  'h6',
+  'blockquote',
+  'p',
+  'a',
+  'ul',
+  'ol',
+  'nl',
+  'li',
+  'b',
+  'i',
+  'strong',
+  'em',
+  'strike',
+  'code',
+  'hr',
+  'br',
+  'div',
+  'table',
+  'thead',
+  'caption',
+  'tbody',
+  'tr',
+  'th',
+  'td',
+  'pre'
 ]
 const allowedAttributes = {
-  a: ["href", "name", "target"],
+  a: ['href', 'name', 'target'],
   // We don't currently allow img itself by default, but this
   // would make sense if we did. You could add srcset here,
   // and if you do the URL is checked for safety
-  img: ["src"]
+  img: ['src']
 }
 
-export default function sanitize(input) {
-  return sanitizeHtml(input, {allowedAttributes, allowedTags})
+export default function sanitize (input) {
+  return sanitizeHtml(input, { allowedAttributes, allowedTags })
 }
diff --git a/front/src/search.js b/front/src/search.js
index 5fc2a6b94..adb2e63fb 100644
--- a/front/src/search.js
+++ b/front/src/search.js
@@ -1,61 +1,61 @@
 export function normalizeQuery (query) {
-	// given a string such as 'this is "my query" go', returns
-	// an array of tokens like this: ['this', 'is', 'my query', 'go']
-	if (!query) {
-		return []
-	}
-	return query.match(/\\?.|^$/g).reduce((p, c) => {
-		if (c === '"'){
-			p.quote ^= 1
-		} else if (!p.quote && c === ' '){
-			p.a.push('')
-		} else {
-			p.a[p.a.length-1] += c.replace(/\\(.)/,"$1")
-		}
-		return p
-	}, {a: ['']}).a
+  // given a string such as 'this is "my query" go', returns
+  // an array of tokens like this: ['this', 'is', 'my query', 'go']
+  if (!query) {
+    return []
+  }
+  return query.match(/\\?.|^$/g).reduce((p, c) => {
+    if (c === '"') {
+      p.quote ^= 1
+    } else if (!p.quote && c === ' ') {
+      p.a.push('')
+    } else {
+      p.a[p.a.length - 1] += c.replace(/\\(.)/, '$1')
+    }
+    return p
+  }, { a: [''] }).a
 }
 
 export function parseTokens (tokens) {
-	// given an array of tokens as returned by normalizeQuery,
-	// returns a list of objects such as [
-	// 	{
-	// 		field: 'status',
-	// 		value: 'pending'
-	// 	},
-	// 	{
-	// 		field: null,
-	// 		value: 'hello'
-	// 	}
-	// ]
-	return tokens.map(t => {
-		// we split the token on ":"
-		let parts = t.split(/:(.+)/)
-		if (parts.length === 1) {
-			// no field specified
-			return {field: null, value: t}
-		}
-		// first item is the field, second is the value, possibly quoted
-		let field = parts[0]
-		let rawValue = parts[1]
+  // given an array of tokens as returned by normalizeQuery,
+  // returns a list of objects such as [
+  //  {
+  //    field: 'status',
+  //    value: 'pending'
+  //  },
+  //  {
+  //    field: null,
+  //    value: 'hello'
+  //  }
+  // ]
+  return tokens.map(t => {
+    // we split the token on ":"
+    const parts = t.split(/:(.+)/)
+    if (parts.length === 1) {
+      // no field specified
+      return { field: null, value: t }
+    }
+    // first item is the field, second is the value, possibly quoted
+    const field = parts[0]
+    let rawValue = parts[1]
 
-		// we remove surrounding quotes if any
-		if (rawValue[0] === '"') {
-			rawValue = rawValue.substring(1)
-		}
-		if (rawValue.slice(-1) === '"') {
-			rawValue = rawValue.substring(0, rawValue.length - 1);
-		}
-		return {field, value: rawValue}
-	})
+    // we remove surrounding quotes if any
+    if (rawValue[0] === '"') {
+      rawValue = rawValue.substring(1)
+    }
+    if (rawValue.slice(-1) === '"') {
+      rawValue = rawValue.substring(0, rawValue.length - 1)
+    }
+    return { field, value: rawValue }
+  })
 }
 
 export function compileTokens (tokens) {
   // given a list of tokens as returned by parseTokens,
   // returns a string query
-  let parts = tokens.map(t => {
+  const parts = tokens.map(t => {
     let v = t.value
-    let k = t.field
+    const k = t.field
     if (v.indexOf(' ') > -1) {
       v = `"${v}"`
     }
diff --git a/front/src/service-worker.js b/front/src/service-worker.js
index 1518363fd..11f73ea72 100644
--- a/front/src/service-worker.js
+++ b/front/src/service-worker.js
@@ -1,32 +1,35 @@
+/* eslint no-undef: "off" */
+
 // This is the code piece that GenerateSW mode can't provide for us.
 // This code listens for the user's confirmation to update the app.
-workbox.loadModule('workbox-routing');
-workbox.loadModule('workbox-strategies');
-workbox.loadModule('workbox-expiration');
+workbox.loadModule('workbox-routing')
+workbox.loadModule('workbox-strategies')
+workbox.loadModule('workbox-expiration')
 
 self.addEventListener('message', (e) => {
   if (!e.data) {
-    return;
+    return
   }
   console.log('[sw] received message', e.data)
   switch (e.data.command) {
     case 'skipWaiting':
-      self.skipWaiting();
-      break;
+      self.skipWaiting()
+      break
     case 'serverChosen':
       self.registerServerRoutes(e.data.serverUrl)
+      break
     default:
       // NOOP
-      break;
+      break
   }
-});
-workbox.core.clientsClaim();
+})
+workbox.core.clientsClaim()
 
-const router = new workbox.routing.Router();
+const router = new workbox.routing.Router()
 router.addCacheListener()
 router.addFetchListener()
 
-var registeredServerRoutes = []
+let registeredServerRoutes = []
 self.registerServerRoutes = (serverUrl) => {
   console.log('[sw] Setting up API caching for', serverUrl)
   registeredServerRoutes.forEach((r) => {
@@ -36,30 +39,30 @@ self.registerServerRoutes = (serverUrl) => {
   if (!serverUrl) {
     return
   }
-  var regexReadyServerUrl = serverUrl.replace('.', '\\.')
+  const regexReadyServerUrl = serverUrl.replace('.', '\\.')
   registeredServerRoutes = []
-  var networkFirstPaths = [
+  const networkFirstPaths = [
     'api/v1/',
-    'media/',
+    'media/'
   ]
-  var networkFirstExcludedPaths = [
+  const networkFirstExcludedPaths = [
     'api/v1/listen'
   ]
-  var strategy = new workbox.strategies.NetworkFirst({
-    cacheName: "api-cache:" + serverUrl,
+  const strategy = new workbox.strategies.NetworkFirst({
+    cacheName: 'api-cache:' + serverUrl,
     plugins: [
       new workbox.expiration.Plugin({
-        maxAgeSeconds: 24 * 60 * 60 * 7,
-      }),
+        maxAgeSeconds: 24 * 60 * 60 * 7
+      })
     ]
   })
-  var networkFirstRoutes = networkFirstPaths.map((path) => {
-    var regex = new RegExp(regexReadyServerUrl + path)
+  const networkFirstRoutes = networkFirstPaths.map((path) => {
+    const regex = new RegExp(regexReadyServerUrl + path)
     return new workbox.routing.RegExpRoute(regex, () => {})
   })
-  var matcher = ({url, event}) => {
+  const matcher = ({ url, event }) => {
     for (let index = 0; index < networkFirstExcludedPaths.length; index++) {
-      const blacklistedPath = networkFirstExcludedPaths[index];
+      const blacklistedPath = networkFirstExcludedPaths[index]
       if (url.pathname.startsWith('/' + blacklistedPath)) {
         // the path is blacklisted, we don't cache it at all
         console.log('[sw] Path is blacklisted, not caching', url.pathname)
@@ -68,8 +71,8 @@ self.registerServerRoutes = (serverUrl) => {
     }
     // we call other regex matchers
     for (let index = 0; index < networkFirstRoutes.length; index++) {
-      const route = networkFirstRoutes[index];
-      let result = route.match({url, event})
+      const route = networkFirstRoutes[index]
+      const result = route.match({ url, event })
       if (result) {
         return result
       }
@@ -77,14 +80,14 @@ self.registerServerRoutes = (serverUrl) => {
     return false
   }
 
-  var route = new workbox.routing.Route(matcher, strategy)
+  const route = new workbox.routing.Route(matcher, strategy)
   console.log('[sw] registering new API route...', route)
   router.registerRoute(route)
   registeredServerRoutes.push(route)
 }
 
 // The precaching code provided by Workbox.
-self.__precacheManifest = [].concat(self.__precacheManifest || []);
+self.__precacheManifest = [].concat(self.__precacheManifest || [])
 
 // workbox.precaching.suppressWarnings(); // Only used with Vue CLI 3 and Workbox v3.
-workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
+workbox.precaching.precacheAndRoute(self.__precacheManifest, {})
diff --git a/front/src/store/auth.js b/front/src/store/auth.js
index 7d7aa6fa6..6df59fd9d 100644
--- a/front/src/store/auth.js
+++ b/front/src/store/auth.js
@@ -133,7 +133,7 @@ export default {
     // Send a request to the login URL and save the returned JWT
     login ({ commit, dispatch }, { next, credentials, onError }) {
       const router = require('@/router').default
-      var form = new FormData()
+      const form = new FormData()
       Object.keys(credentials).forEach((k) => {
         form.set(k, credentials[k])
       })
diff --git a/front/src/store/channels.js b/front/src/store/channels.js
index d6c14f835..d5189fa7f 100644
--- a/front/src/store/channels.js
+++ b/front/src/store/channels.js
@@ -9,17 +9,17 @@ export default {
     showUploadModal: false,
     latestPublication: null,
     uploadModalConfig: {
-      channel: null,
+      channel: null
     }
   },
   mutations: {
-    subscriptions: (state, {uuid, value}) => {
+    subscriptions: (state, { uuid, value }) => {
       if (value) {
         if (state.subscriptions.indexOf(uuid) === -1) {
           state.subscriptions.push(uuid)
         }
       } else {
-        let i = state.subscriptions.indexOf(uuid)
+        const i = state.subscriptions.indexOf(uuid)
         if (i > -1) {
           state.subscriptions.splice(i, 1)
         }
@@ -38,11 +38,11 @@ export default {
         }
       }
     },
-    publish (state, {uploads, channel}) {
+    publish (state, { uploads, channel }) {
       state.latestPublication = {
         date: new Date(),
         uploads,
-        channel,
+        channel
       }
       state.showUploadModal = false
     }
@@ -53,32 +53,32 @@ export default {
     }
   },
   actions: {
-    set ({commit, state}, {uuid, value}) {
-      commit('subscriptions', {uuid, value})
+    set ({ commit, state }, { uuid, value }) {
+      commit('subscriptions', { uuid, value })
       if (value) {
         return axios.post(`channels/${uuid}/subscribe/`).then((response) => {
           logger.default.info('Successfully subscribed to channel')
         }, (response) => {
           logger.default.info('Error while subscribing to channel')
-          commit('subscriptions', {uuid, value: !value})
+          commit('subscriptions', { uuid, value: !value })
         })
       } else {
         return axios.post(`channels/${uuid}/unsubscribe/`).then((response) => {
           logger.default.info('Successfully unsubscribed from channel')
         }, (response) => {
           logger.default.info('Error while unsubscribing from channel')
-          commit('subscriptions', {uuid, value: !value})
+          commit('subscriptions', { uuid, value: !value })
         })
       }
     },
-    toggle ({getters, dispatch}, uuid) {
-      dispatch('set', {uuid, value: !getters['isSubscribed'](uuid)})
+    toggle ({ getters, dispatch }, uuid) {
+      dispatch('set', { uuid, value: !getters.isSubscribed(uuid) })
     },
-    fetchSubscriptions ({dispatch, state, commit, rootState}, url) {
-      let promise = axios.get('subscriptions/all/')
+    fetchSubscriptions ({ dispatch, state, commit, rootState }, url) {
+      const promise = axios.get('subscriptions/all/')
       return promise.then((response) => {
         response.data.results.forEach(result => {
-          commit('subscriptions', {uuid: result.channel, value: true})
+          commit('subscriptions', { uuid: result.channel, value: true })
         })
       })
     }
diff --git a/front/src/store/favorites.js b/front/src/store/favorites.js
index 1d1302eb6..7dede7b58 100644
--- a/front/src/store/favorites.js
+++ b/front/src/store/favorites.js
@@ -8,13 +8,13 @@ export default {
     count: 0
   },
   mutations: {
-    track: (state, {id, value}) => {
+    track: (state, { id, value }) => {
       if (value) {
         if (state.tracks.indexOf(id) === -1) {
           state.tracks.push(id)
         }
       } else {
-        let i = state.tracks.indexOf(id)
+        const i = state.tracks.indexOf(id)
         if (i > -1) {
           state.tracks.splice(i, 1)
         }
@@ -32,39 +32,39 @@ export default {
     }
   },
   actions: {
-    set ({commit, state}, {id, value}) {
-      commit('track', {id, value})
+    set ({ commit, state }, { id, value }) {
+      commit('track', { id, value })
       if (value) {
-        return axios.post('favorites/tracks/', {'track': id}).then((response) => {
+        return axios.post('favorites/tracks/', { track: id }).then((response) => {
           logger.default.info('Successfully added track to favorites')
         }, (response) => {
           logger.default.info('Error while adding track to favorites')
-          commit('track', {id, value: !value})
+          commit('track', { id, value: !value })
         })
       } else {
-        return axios.post('favorites/tracks/remove/', {'track': id}).then((response) => {
+        return axios.post('favorites/tracks/remove/', { track: id }).then((response) => {
           logger.default.info('Successfully removed track from favorites')
         }, (response) => {
           logger.default.info('Error while removing track from favorites')
-          commit('track', {id, value: !value})
+          commit('track', { id, value: !value })
         })
       }
     },
-    toggle ({getters, dispatch}, id) {
-      dispatch('set', {id, value: !getters['isFavorite'](id)})
+    toggle ({ getters, dispatch }, id) {
+      dispatch('set', { id, value: !getters.isFavorite(id) })
     },
-    fetch ({dispatch, state, commit, rootState}, url) {
+    fetch ({ dispatch, state, commit, rootState }, url) {
       // will fetch favorites by batches from API to have them locally
-      let params = {
+      const params = {
         user: rootState.auth.profile.id,
         page_size: 50,
         ordering: '-creation_date'
       }
-      let promise = axios.get('favorites/tracks/all/', {params: params})
+      const promise = axios.get('favorites/tracks/all/', { 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, value: true })
         })
       })
     }
diff --git a/front/src/store/index.js b/front/src/store/index.js
index 33323749f..a79544203 100644
--- a/front/src/store/index.js
+++ b/front/src/store/index.js
@@ -75,18 +75,18 @@ export default new Vuex.Store({
             tracks: state.queue.tracks.map(track => {
               // we keep only valuable fields to make the cache lighter and avoid
               // cyclic value serialization errors
-              let artist = {
+              const artist = {
                 id: track.artist.id,
                 mbid: track.artist.mbid,
                 name: track.artist.name
               }
-              let data = {
+              const data = {
                 id: track.id,
                 title: track.title,
                 mbid: track.mbid,
                 uploads: track.uploads,
                 listen_url: track.listen_url,
-                artist: artist,
+                artist: artist
               }
               if (track.album) {
                 data.album = {
diff --git a/front/src/store/instance.js b/front/src/store/instance.js
index 1f251bca9..c7e610a6f 100644
--- a/front/src/store/instance.js
+++ b/front/src/store/instance.js
@@ -52,15 +52,15 @@ export default {
       },
       moderation: {
         signup_approval_enabled: {
-          value: false,
+          value: false
         },
-        signup_form_customization: {value: null}
+        signup_form_customization: { value: null }
       },
       subsonic: {
         enabled: {
           value: true
         }
-      },
+      }
     }
   },
   mutations: {
@@ -87,12 +87,12 @@ export default {
         value = value + '/'
       }
       state.instanceUrl = value
-      notifyServiceWorker(state.registration, {command: 'serverChosen', serverUrl: state.instanceUrl})
+      notifyServiceWorker(state.registration, { command: 'serverChosen', serverUrl: state.instanceUrl })
       // append the URL to the list (and remove existing one if needed)
       if (value) {
-        let index = state.knownInstances.indexOf(value);
+        const index = state.knownInstances.indexOf(value)
         if (index > -1) {
-          state.knownInstances.splice(index, 1);
+          state.knownInstances.splice(index, 1)
         }
         state.knownInstances.splice(0, 0, value)
       }
@@ -100,7 +100,7 @@ export default {
         axios.defaults.baseURL = null
         return
       }
-      let suffix = 'api/v1/'
+      const suffix = 'api/v1/'
       axios.defaults.baseURL = state.instanceUrl + suffix
     }
   },
@@ -116,12 +116,12 @@ export default {
         relativeUrl = relativeUrl.slice(1)
       }
 
-      let instanceUrl = state.instanceUrl || getDefaultUrl()
+      const instanceUrl = state.instanceUrl || getDefaultUrl()
       return instanceUrl + relativeUrl
     },
     domain: (state) => {
-      let url = state.instanceUrl
-      let parser = document.createElement("a")
+      const url = state.instanceUrl
+      const parser = document.createElement('a')
       parser.href = url
       return parser.hostname
     },
@@ -130,9 +130,9 @@ export default {
     }
   },
   actions: {
-    setUrl ({commit, dispatch}, url) {
+    setUrl ({ commit, dispatch }, url) {
       commit('instanceUrl', url)
-      let modules = [
+      const modules = [
         'auth',
         'favorites',
         'moderation',
@@ -142,14 +142,14 @@ export default {
         'radios'
       ]
       modules.forEach(m => {
-        commit(`${m}/reset`, null, {root: true})
+        commit(`${m}/reset`, null, { root: true })
       })
     },
     // Send a request to the login URL and save the returned JWT
-    fetchSettings ({commit}, payload) {
+    fetchSettings ({ commit }, payload) {
       return axios.get('instance/settings/').then(response => {
         logger.default.info('Successfully fetched instance settings')
-        let sections = {}
+        const sections = {}
         response.data.forEach(e => {
           sections[e.section] = {}
         })
@@ -164,7 +164,7 @@ export default {
         logger.default.error('Error while fetching settings', response.data)
       })
     },
-    fetchFrontSettings ({commit}) {
+    fetchFrontSettings ({ commit }) {
       return axios.get('/front/settings.json').then(response => {
         commit('frontSettings', response.data)
       }, response => {
diff --git a/front/src/store/libraries.js b/front/src/store/libraries.js
index 77632de8b..5e002f36c 100644
--- a/front/src/store/libraries.js
+++ b/front/src/store/libraries.js
@@ -6,18 +6,18 @@ export default {
   state: {
     followedLibraries: [],
     followsByLibrary: {},
-    count: 0,
+    count: 0
   },
   mutations: {
-    follows: (state, {library, follow}) => {
-      let replacement = {...state.followsByLibrary}
+    follows: (state, { library, follow }) => {
+      const replacement = { ...state.followsByLibrary }
       if (follow) {
         if (state.followedLibraries.indexOf(library) === -1) {
           state.followedLibraries.push(library)
           replacement[library] = follow
         }
       } else {
-        let i = state.followedLibraries.indexOf(library)
+        const i = state.followedLibraries.indexOf(library)
         if (i > -1) {
           state.followedLibraries.splice(i, 1)
           replacement[library] = null
@@ -30,7 +30,7 @@ export default {
       state.followedLibraries = []
       state.followsByLibrary = {}
       state.count = 0
-    },
+    }
   },
   getters: {
     follow: (state) => (library) => {
@@ -38,34 +38,34 @@ export default {
     }
   },
   actions: {
-    set ({commit, state}, {uuid, value}) {
+    set ({ commit, state }, { uuid, value }) {
       if (value) {
-        return axios.post(`federation/follows/library/`, {target: uuid}).then((response) => {
+        return axios.post('federation/follows/library/', { target: uuid }).then((response) => {
           logger.default.info('Successfully subscribed to library')
-          commit('follows', {library: uuid, follow: response.data})
+          commit('follows', { library: uuid, follow: response.data })
         }, (response) => {
           logger.default.info('Error while subscribing to library')
-          commit('follows', {library: uuid, follow: null})
+          commit('follows', { library: uuid, follow: null })
         })
       } else {
-        let follow = state.followsByLibrary[uuid]
+        const follow = state.followsByLibrary[uuid]
         return axios.delete(`federation/follows/library/${follow.uuid}/`).then((response) => {
           logger.default.info('Successfully unsubscribed from library')
-          commit('follows', {library: uuid, follow: null})
+          commit('follows', { library: uuid, follow: null })
         }, (response) => {
           logger.default.info('Error while unsubscribing from library')
-          commit('follows', {library: uuid, follow: follow})
+          commit('follows', { library: uuid, follow: follow })
         })
       }
     },
-    toggle ({getters, dispatch}, uuid) {
-      dispatch('set', {uuid, value: !getters['follow'](uuid)})
+    toggle ({ getters, dispatch }, uuid) {
+      dispatch('set', { uuid, value: !getters.follow(uuid) })
     },
-    fetchFollows ({dispatch, state, commit, rootState}, url) {
-      let promise = axios.get('federation/follows/library/all/')
+    fetchFollows ({ dispatch, state, commit, rootState }, url) {
+      const promise = axios.get('federation/follows/library/all/')
       return promise.then((response) => {
         response.data.results.forEach(result => {
-          commit('follows', {library: result.library, follow: result})
+          commit('follows', { library: result.library, follow: result })
         })
       })
     }
diff --git a/front/src/store/moderation.js b/front/src/store/moderation.js
index 16caf60aa..6bb499940 100644
--- a/front/src/store/moderation.js
+++ b/front/src/store/moderation.js
@@ -11,11 +11,11 @@ export default {
     lastUpdate: new Date(),
     filterModalTarget: {
       type: null,
-      target: null,
+      target: null
     },
     reportModalTarget: {
       type: null,
-      target: null,
+      target: null
     }
   },
   mutations: {
@@ -39,7 +39,7 @@ export default {
       if (!value) {
         state.filterModalTarget = {
           type: null,
-          target: null,
+          target: null
         }
       }
     },
@@ -48,7 +48,7 @@ export default {
       if (!value) {
         state.reportModalTarget = {
           type: null,
-          target: null,
+          target: null
         }
       }
     },
@@ -61,41 +61,41 @@ export default {
     },
     deleteContentFilter (state, uuid) {
       state.filters = state.filters.filter((e) => {
-        return e.uuid != uuid
+        return e.uuid !== uuid
       })
     }
   },
   getters: {
     artistFilters: (state) => () => {
-      let f = state.filters.filter((f) => {
+      const f = state.filters.filter((f) => {
         return f.target.type === 'artist'
       })
-      let p = _.sortBy(f, [(e) => { return e.creation_date }])
+      const p = _.sortBy(f, [(e) => { return e.creation_date }])
       p.reverse()
       return p
-    },
+    }
   },
   actions: {
-    hide ({commit}, payload) {
+    hide ({ commit }, payload) {
       commit('filterModalTarget', payload)
       commit('showFilterModal', true)
     },
-    report ({commit}, payload) {
+    report ({ commit }, payload) {
       commit('reportModalTarget', payload)
       commit('showReportModal', true)
     },
-    fetchContentFilters ({dispatch, state, commit, rootState}, url) {
+    fetchContentFilters ({ dispatch, state, commit, rootState }, url) {
       let params = {}
       let promise
       if (url) {
         promise = axios.get(url)
       } else {
-          commit('empty')
-          params = {
-            page_size: 100,
-            ordering: '-creation_date'
-          }
-          promise = axios.get('moderation/content-filters/', {params: params})
+        commit('empty')
+        params = {
+          page_size: 100,
+          ordering: '-creation_date'
+        }
+        promise = axios.get('moderation/content-filters/', { params: params })
       }
       return promise.then((response) => {
         logger.default.info('Fetched a batch of ' + response.data.results.length + ' filters')
@@ -107,8 +107,8 @@ export default {
         })
       })
     },
-    deleteContentFilter ({commit}, uuid) {
-      return axios.delete(`moderation/content-filters/${ uuid }/`).then((response) => {
+    deleteContentFilter ({ commit }, uuid) {
+      return axios.delete(`moderation/content-filters/${uuid}/`).then((response) => {
         commit('deleteContentFilter', uuid)
       })
     }
diff --git a/front/src/store/playlists.js b/front/src/store/playlists.js
index 6c91208dc..1c55a71b5 100644
--- a/front/src/store/playlists.js
+++ b/front/src/store/playlists.js
@@ -25,18 +25,17 @@ export default {
     }
   },
   actions: {
-    async fetchOwn ({commit, rootState}) {
-      let userId = rootState.auth.profile.id
+    async fetchOwn ({ commit, rootState }) {
+      const userId = rootState.auth.profile.id
       if (!userId) {
         return
       }
       let playlists = []
       let url = 'playlists/'
       while (url != null) {
-        let response = await axios.get(url, {params: {scope: "me"}})
+        const response = await axios.get(url, { params: { scope: 'me' } })
         playlists = [...playlists, ...response.data.results]
         url = response.data.next
-
       }
       commit('playlists', playlists)
     }
diff --git a/front/src/store/queue.js b/front/src/store/queue.js
index e096a6f0a..86e4f6443 100644
--- a/front/src/store/queue.js
+++ b/front/src/store/queue.js
@@ -6,7 +6,7 @@ export default {
   state: {
     tracks: [],
     currentIndex: -1,
-    ended: true,
+    ended: true
   },
   mutations: {
     reset (state) {
@@ -20,16 +20,16 @@ export default {
     ended (state, value) {
       state.ended = value
     },
-    splice (state, {start, size}) {
+    splice (state, { start, size }) {
       state.tracks.splice(start, size)
     },
     tracks (state, value) {
       state.tracks = value
     },
-    insert (state, {track, index}) {
+    insert (state, { track, index }) {
       state.tracks.splice(index, 0, track)
     },
-    reorder (state, {tracks, oldIndex, newIndex}) {
+    reorder (state, { tracks, oldIndex, newIndex }) {
       // called when the user uses drag / drop to reorder
       // tracks in queue
       state.tracks = tracks
@@ -60,18 +60,18 @@ export default {
     isEmpty: state => state.tracks.length === 0
   },
   actions: {
-    append ({commit, state, dispatch}, {track, index}) {
+    append ({ commit, state, dispatch }, { track, index }) {
       index = index || state.tracks.length
       if (index > state.tracks.length - 1) {
         // we simply push to the end
-        commit('insert', {track, index: state.tracks.length})
+        commit('insert', { track, index: state.tracks.length })
       } else {
         // we insert the track at given position
-        commit('insert', {track, index})
+        commit('insert', { track, index })
       }
     },
 
-    appendMany ({state, commit, dispatch}, {tracks, index, callback}) {
+    appendMany ({ state, commit, dispatch }, { tracks, index, callback }) {
       logger.default.info('Appending many tracks to the queue', tracks.map(e => { return e.title }))
       let shouldPlay = false
       if (state.tracks.length === 0) {
@@ -80,9 +80,9 @@ export default {
       } else {
         index = index || state.tracks.length
       }
-      let total = tracks.length
+      const total = tracks.length
       tracks.forEach((t, i) => {
-        let p = dispatch('append', {track: t, index: index})
+        const p = dispatch('append', { track: t, index: index })
         index += 1
         if (callback && i + 1 === total) {
           p.then(callback)
@@ -95,13 +95,13 @@ export default {
       })
     },
 
-    cleanTrack ({state, dispatch, commit}, index) {
+    cleanTrack ({ state, dispatch, commit }, index) {
       // are we removing current playin track
       const current = index === state.currentIndex
       if (current) {
-        dispatch('player/stop', null, {root: true})
+        dispatch('player/stop', null, { root: true })
       }
-      commit('splice', {start: index, size: 1})
+      commit('splice', { start: index, size: 1 })
       if (index < state.currentIndex) {
         commit('currentIndex', state.currentIndex - 1)
       } else if (index > 0 && index === state.tracks.length && current) {
@@ -115,18 +115,18 @@ export default {
         commit('currentIndex', index)
       }
       if (state.currentIndex + 1 === state.tracks.length) {
-        dispatch('radios/populateQueue', null, {root: true})
+        dispatch('radios/populateQueue', null, { root: true })
       }
     },
 
-    previous ({state, dispatch, rootState}) {
+    previous ({ state, dispatch, rootState }) {
       if (state.currentIndex > 0 && rootState.player.currentTime < 3) {
         dispatch('currentIndex', state.currentIndex - 1)
       } else {
         dispatch('currentIndex', state.currentIndex)
       }
     },
-    next ({state, dispatch, commit, rootState}) {
+    next ({ state, dispatch, commit, rootState }) {
       if (rootState.player.looping === 2 && state.currentIndex >= state.tracks.length - 1) {
         logger.default.info('Going back to the beginning of the queue')
         return dispatch('currentIndex', 0)
@@ -139,29 +139,29 @@ export default {
         }
       }
     },
-    last ({state, dispatch}) {
+    last ({ state, dispatch }) {
       dispatch('currentIndex', state.tracks.length - 1)
     },
-    currentIndex ({commit, state, rootState, dispatch}, index) {
+    currentIndex ({ commit, state, rootState, dispatch }, index) {
       commit('ended', false)
-      commit('player/currentTime', 0, {root: true})
+      commit('player/currentTime', 0, { root: true })
       commit('currentIndex', index)
       if (state.tracks.length - index <= 2 && rootState.radios.running) {
-        dispatch('radios/populateQueue', null, {root: true})
+        dispatch('radios/populateQueue', null, { root: true })
       }
     },
-    clean ({dispatch, commit}) {
-      dispatch('radios/stop', null, {root: true})
-      dispatch('player/stop', null, {root: true})
+    clean ({ dispatch, commit }) {
+      dispatch('radios/stop', null, { root: true })
+      dispatch('player/stop', null, { root: true })
       commit('tracks', [])
       dispatch('currentIndex', -1)
       // so we replay automatically on next track append
       commit('ended', true)
     },
-    async shuffle ({dispatch, commit, state}, callback) {
-      let shuffled = _.shuffle(state.tracks)
+    async shuffle ({ dispatch, commit, state }, callback) {
+      const shuffled = _.shuffle(state.tracks)
       commit('tracks', [])
-      let params = {tracks: shuffled}
+      const params = { tracks: shuffled }
       if (callback) {
         params.callback = callback
       }
diff --git a/front/src/store/radios.js b/front/src/store/radios.js
index 475b2457f..06f5ee2da 100644
--- a/front/src/store/radios.js
+++ b/front/src/store/radios.js
@@ -1,7 +1,7 @@
 import axios from 'axios'
 import logger from '@/logging'
 
-import {getClientOnlyRadio} from '@/radios'
+import { getClientOnlyRadio } from '@/radios'
 
 export default {
   namespaced: true,
@@ -14,7 +14,7 @@ export default {
       return {
         'actor-content': {
           name: 'Your content',
-          description: "Picks from your own libraries"
+          description: 'Picks from your own libraries'
         },
         random: {
           name: 'Random',
@@ -30,7 +30,7 @@ export default {
         },
         'recently-added': {
           name: 'Recently Added',
-          description: "Newest content on the network. Get some fresh air."
+          description: 'Newest content on the network. Get some fresh air.'
         }
       }
     }
@@ -48,53 +48,53 @@ export default {
     }
   },
   actions: {
-    start ({commit, dispatch}, {type, objectId, customRadioId, clientOnly}) {
-      var params = {
+    start ({ commit, dispatch }, { type, objectId, customRadioId, clientOnly }) {
+      const params = {
         radio_type: type,
         related_object_id: objectId,
-        custom_radio: customRadioId,
+        custom_radio: customRadioId
       }
       if (clientOnly) {
-        commit('current', {type, objectId, customRadioId, clientOnly})
+        commit('current', { type, objectId, customRadioId, clientOnly })
         commit('running', true)
         dispatch('populateQueue', true)
         return
       }
       return axios.post('radios/sessions/', params).then((response) => {
         logger.default.info('Successfully started radio ', type)
-        commit('current', {type, objectId, session: response.data.id, customRadioId})
+        commit('current', { type, objectId, session: response.data.id, customRadioId })
         commit('running', true)
         dispatch('populateQueue', true)
       }, (response) => {
         logger.default.error('Error while starting radio', type)
       })
     },
-    stop ({commit, state}) {
+    stop ({ commit, state }) {
       if (state.current && state.current.clientOnly) {
         getClientOnlyRadio(state.current).stop()
       }
       commit('current', null)
       commit('running', false)
     },
-    populateQueue ({rootState, state, dispatch}, playNow) {
+    populateQueue ({ rootState, state, dispatch }, playNow) {
       if (!state.running) {
         return
       }
       if (rootState.player.errorCount >= rootState.player.maxConsecutiveErrors - 1) {
         return
       }
-      var params = {
+      const params = {
         session: state.current.session
       }
       if (state.current.clientOnly) {
-        return getClientOnlyRadio(state.current).populateQueue({current: state.current, dispatch, state, rootState, playNow})
+        return getClientOnlyRadio(state.current).populateQueue({ current: state.current, dispatch, state, rootState, playNow })
       }
       return axios.post('radios/tracks/', params).then((response) => {
         logger.default.info('Adding track to queue from radio')
-        let append = dispatch('queue/append', {track: response.data.track}, {root: true})
+        const append = dispatch('queue/append', { track: response.data.track }, { root: true })
         if (playNow) {
           append.then(() => {
-            dispatch('queue/last', null, {root: true})
+            dispatch('queue/last', null, { root: true })
           })
         }
       }, (response) => {
diff --git a/front/src/store/ui.js b/front/src/store/ui.js
index 2646df3ee..5738f8ec7 100644
--- a/front/src/store/ui.js
+++ b/front/src/store/ui.js
@@ -11,18 +11,18 @@ export default {
     lastDate: new Date(),
     maxMessages: 100,
     messageDisplayDuration: 5 * 1000,
-    supportedExtensions: ["flac", "ogg", "mp3", "opus", "aac", "m4a", "aiff", "aif"],
+    supportedExtensions: ['flac', 'ogg', 'mp3', 'opus', 'aac', 'm4a', 'aiff', 'aif'],
     messages: [],
     theme: 'light',
     window: {
       height: 0,
-      width: 0,
+      width: 0
     },
     notifications: {
       inbox: 0,
       pendingReviewEdits: 0,
       pendingReviewReports: 0,
-      pendingReviewRequests: 0,
+      pendingReviewRequests: 0
     },
     websocketEventsHandlers: {
       'inbox.item_added': {},
@@ -31,95 +31,95 @@ export default {
       'mutation.updated': {},
       'report.created': {},
       'user_request.created': {},
-      'Listen': {},
+      Listen: {}
     },
     pageTitle: null,
     routePreferences: {
-      "library.albums.browse": {
+      'library.albums.browse': {
         paginateBy: 25,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "library.artists.browse": {
+      'library.artists.browse': {
         paginateBy: 30,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "library.podcasts.browse": {
+      'library.podcasts.browse': {
         paginateBy: 30,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "library.radios.browse": {
+      'library.radios.browse': {
         paginateBy: 12,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "library.playlists.browse": {
+      'library.playlists.browse': {
         paginateBy: 25,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "library.albums.me": {
+      'library.albums.me': {
         paginateBy: 25,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "library.artists.me": {
+      'library.artists.me': {
         paginateBy: 30,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "library.radios.me": {
+      'library.radios.me': {
         paginateBy: 12,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "library.playlists.me": {
+      'library.playlists.me': {
         paginateBy: 25,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "content.libraries.files": {
+      'content.libraries.files': {
         paginateBy: 50,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "library.detail.upload": {
+      'library.detail.upload': {
         paginateBy: 50,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "library.detail.edit": {
+      'library.detail.edit': {
         paginateBy: 50,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "library.detail": {
+      'library.detail': {
         paginateBy: 50,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "favorites": {
+      favorites: {
         paginateBy: 50,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "manage.moderation.requests.list": {
+      'manage.moderation.requests.list': {
         paginateBy: 25,
-        orderingDirection: "-",
-        ordering: "creation_date",
+        orderingDirection: '-',
+        ordering: 'creation_date'
       },
-      "manage.moderation.reports.list": {
+      'manage.moderation.reports.list': {
         paginateBy: 25,
-        orderingDirection: "-",
-        ordering: "creation_date",
-      },
+        orderingDirection: '-',
+        ordering: 'creation_date'
+      }
     },
     serviceWorker: {
       refreshing: false,
       registration: null,
-      updateAvailable: false,
+      updateAvailable: false
     }
   },
   getters: {
@@ -130,7 +130,7 @@ export default {
       if (!rootState.instance.settings.instance.support_message.value) {
         return false
       }
-      let displayDate = rootState.auth.profile.instance_support_message_display_date
+      const displayDate = rootState.auth.profile.instance_support_message_display_date
       if (!displayDate) {
         return false
       }
@@ -143,7 +143,7 @@ export default {
       if (!rootState.instance.settings.instance.funkwhale_support_message_enabled.value) {
         return false
       }
-      let displayDate = rootState.auth.profile.funkwhale_support_message_display_date
+      const displayDate = rootState.auth.profile.funkwhale_support_message_display_date
       if (!displayDate) {
         return false
       }
@@ -163,21 +163,20 @@ export default {
     windowSize: (state, getters) => {
       // IMPORTANT: if you modify these breakpoints, also modify the values in
       // style/vendor/_media.scss
-      let width = state.window.width
-      let breakpoints = [
-        {name: 'widedesktop', width: 1200},
-        {name: 'desktop', width: 1024},
-        {name: 'tablet', width: 768},
-        {name: 'phone', width: 320},
+      const width = state.window.width
+      const breakpoints = [
+        { name: 'widedesktop', width: 1200 },
+        { name: 'desktop', width: 1024 },
+        { name: 'tablet', width: 768 },
+        { name: 'phone', width: 320 }
       ]
       for (let index = 0; index < breakpoints.length; index++) {
-        const element = breakpoints[index];
+        const element = breakpoints[index]
         if (width >= element.width) {
           return element.name
         }
       }
       return 'phone'
-
     },
     layoutVersion: (state, getters) => {
       if (['tablet', 'phone'].indexOf(getters.windowSize) > -1) {
@@ -188,10 +187,10 @@ export default {
     }
   },
   mutations: {
-    addWebsocketEventHandler: (state, {eventName, id, handler}) => {
+    addWebsocketEventHandler: (state, { eventName, id, handler }) => {
       state.websocketEventsHandlers[eventName][id] = handler
     },
-    removeWebsocketEventHandler: (state, {eventName, id}) => {
+    removeWebsocketEventHandler: (state, { eventName, id }) => {
       delete state.websocketEventsHandlers[eventName][id]
     },
     currentLanguage: (state, value) => {
@@ -213,14 +212,14 @@ export default {
       state.theme = value
     },
     addMessage (state, message) {
-      let finalMessage = {
+      const finalMessage = {
         displayTime: state.messageDisplayDuration,
         key: String(new Date()),
-        ...message,
+        ...message
       }
-      let key = finalMessage.key
+      const key = finalMessage.key
       state.messages = state.messages.filter((m) => {
-        return m.key != key
+        return m.key !== key
       })
       state.messages.push(finalMessage)
       if (state.messages.length > state.maxMessages) {
@@ -229,15 +228,15 @@ export default {
     },
     removeMessage (state, key) {
       state.messages = state.messages.filter((m) => {
-        return m.key != key
+        return m.key !== key
       })
     },
-    notifications (state, {type, count}) {
+    notifications (state, { type, count }) {
       state.notifications[type] = count
     },
-    incrementNotifications (state, {type, count, value}) {
-      if (value != undefined) {
-          state.notifications[type] = Math.max(0, value)
+    incrementNotifications (state, { type, count, value }) {
+      if (value !== undefined) {
+        state.notifications[type] = Math.max(0, value)
       } else {
         state.notifications[type] = Math.max(0, state.notifications[type] + count)
       }
@@ -245,77 +244,77 @@ export default {
     pageTitle: (state, value) => {
       state.pageTitle = value
     },
-    paginateBy: (state, {route, value}) => {
+    paginateBy: (state, { route, value }) => {
       state.routePreferences[route].paginateBy = value
     },
-    ordering: (state, {route, value}) => {
+    ordering: (state, { route, value }) => {
       state.routePreferences[route].ordering = value
     },
-    orderingDirection: (state, {route, value}) => {
+    orderingDirection: (state, { route, value }) => {
       state.routePreferences[route].orderingDirection = value
     },
 
     serviceWorker: (state, value) => {
-      state.serviceWorker = {...state.serviceWorker, ...value}
+      state.serviceWorker = { ...state.serviceWorker, ...value }
     },
     window: (state, value) => {
       state.window = value
     }
   },
   actions: {
-    fetchUnreadNotifications ({commit}, payload) {
-      axios.get('federation/inbox/', {params: {is_read: false, page_size: 1}}).then((response) => {
-        commit('notifications', {type: 'inbox', count: response.data.count})
+    fetchUnreadNotifications ({ commit }, payload) {
+      axios.get('federation/inbox/', { params: { is_read: false, page_size: 1 } }).then((response) => {
+        commit('notifications', { type: 'inbox', count: response.data.count })
       })
     },
-    fetchPendingReviewEdits ({commit, rootState}, payload) {
-      axios.get('mutations/', {params: {is_approved: 'null', page_size: 1}}).then((response) => {
-        commit('notifications', {type: 'pendingReviewEdits', count: response.data.count})
+    fetchPendingReviewEdits ({ commit, rootState }, payload) {
+      axios.get('mutations/', { params: { is_approved: 'null', page_size: 1 } }).then((response) => {
+        commit('notifications', { type: 'pendingReviewEdits', count: response.data.count })
       })
     },
-    fetchPendingReviewReports ({commit, rootState}, payload) {
-      axios.get('manage/moderation/reports/', {params: {is_handled: 'false', page_size: 1}}).then((response) => {
-        commit('notifications', {type: 'pendingReviewReports', count: response.data.count})
+    fetchPendingReviewReports ({ commit, rootState }, payload) {
+      axios.get('manage/moderation/reports/', { params: { is_handled: 'false', page_size: 1 } }).then((response) => {
+        commit('notifications', { type: 'pendingReviewReports', count: response.data.count })
       })
     },
-    fetchPendingReviewRequests ({commit, rootState}, payload) {
-      axios.get('manage/moderation/requests/', {params: {status: 'pending', page_size: 1}}).then((response) => {
-        commit('notifications', {type: 'pendingReviewRequests', count: response.data.count})
+    fetchPendingReviewRequests ({ commit, rootState }, payload) {
+      axios.get('manage/moderation/requests/', { params: { status: 'pending', page_size: 1 } }).then((response) => {
+        commit('notifications', { type: 'pendingReviewRequests', count: response.data.count })
       })
     },
 
-    async currentLanguage ({state, commit, rootState}, value) {
-      commit("currentLanguage", value)
+    async currentLanguage ({ state, commit, rootState }, value) {
+      commit('currentLanguage', value)
       if (rootState.auth.authenticated) {
-        await axios.post("users/settings", {"language": value})
+        await axios.post('users/settings', { language: value })
       }
     },
 
-    async theme ({state, commit, rootState}, value) {
-      commit("theme", value)
+    async theme ({ state, commit, rootState }, value) {
+      commit('theme', value)
       if (rootState.auth.authenticated) {
-        await axios.post("users/settings", {"theme": value})
+        await axios.post('users/settings', { theme: value })
       }
     },
 
-    async initSettings ({commit}, settings) {
+    async initSettings ({ commit }, settings) {
       settings = settings || {}
       if (settings.language) {
-        commit("currentLanguage", settings.language)
+        commit('currentLanguage', settings.language)
       }
       if (settings.theme) {
-        commit("theme", settings.theme)
+        commit('theme', settings.theme)
       }
     },
-    websocketEvent ({state}, event) {
-      let handlers = state.websocketEventsHandlers[event.type]
+    websocketEvent ({ state }, event) {
+      const handlers = state.websocketEventsHandlers[event.type]
       console.log('Dispatching websocket event', event, handlers)
       if (!handlers) {
         return
       }
-      let names = Object.keys(handlers)
+      const names = Object.keys(handlers)
       names.forEach((k) => {
-        let handler = handlers[k]
+        const handler = handlers[k]
         handler(event)
       })
     }
diff --git a/front/src/utils.js b/front/src/utils.js
index 596ec07d5..11180497b 100644
--- a/front/src/utils.js
+++ b/front/src/utils.js
@@ -1,17 +1,17 @@
 import lodash from '@/lodash'
 
-export function setUpdate(obj, statuses, value) {
-  let updatedKeys = lodash.keys(obj)
+export function setUpdate (obj, statuses, value) {
+  const updatedKeys = lodash.keys(obj)
   updatedKeys.forEach((k) => {
     statuses[k] = value
   })
 }
 
-export function parseAPIErrors(responseData, parentField) {
+export function parseAPIErrors (responseData, parentField) {
   let errors = []
-  for (var field in responseData) {
-    if (responseData.hasOwnProperty(field)) {
-      let value = responseData[field]
+  for (const field in responseData) {
+    if (Object.prototype.hasOwnProperty.call(responseData, field)) {
+      const value = responseData[field]
       let fieldName = lodash.startCase(field.replace('_', ' '))
       if (parentField) {
         fieldName = `${parentField} - ${fieldName}`
@@ -26,7 +26,7 @@ export function parseAPIErrors(responseData, parentField) {
         })
       } else if (typeof value === 'object') {
         // nested errors
-        let nestedErrors = parseAPIErrors(value, fieldName)
+        const nestedErrors = parseAPIErrors(value, fieldName)
         errors = [...errors, ...nestedErrors]
       }
     }
@@ -34,13 +34,13 @@ export function parseAPIErrors(responseData, parentField) {
   return errors
 }
 
-export function getCookie(name) {
+export function getCookie (name) {
   return document.cookie
-  .split('; ')
-  .find(row => row.startsWith(name))
-  .split('=')[1];
+    .split('; ')
+    .find(row => row.startsWith(name))
+    .split('=')[1]
 }
-export function setCsrf(xhr) {
+export function setCsrf (xhr) {
   if (getCookie('csrftoken')) {
     xhr.setRequestHeader('X-CSRFToken', getCookie('csrftoken'))
   }
@@ -48,12 +48,12 @@ export function setCsrf(xhr) {
 
 export function checkRedirectToLogin (store, router) {
   if (!store.state.auth.authenticated) {
-    router.push({name: 'login', query: {next: router.currentRoute.fullPath}})
+    router.push({ name: 'login', query: { next: router.currentRoute.fullPath } })
   }
 }
 
 export function getDomain (url) {
-  let parser = document.createElement("a")
+  const parser = document.createElement('a')
   parser.href = url
   return parser.hostname
-}
\ No newline at end of file
+}
diff --git a/front/src/utils/color.js b/front/src/utils/color.js
index 8066abd3c..b4ad6d8d2 100644
--- a/front/src/utils/color.js
+++ b/front/src/utils/color.js
@@ -1,12 +1,12 @@
 export function hashCode (str) { // java String#hashCode
-  var hash = 0
-  for (var i = 0; i < str.length; i++) {
+  let hash = 0
+  for (let i = 0; i < str.length; i++) {
     hash = str.charCodeAt(i) + ((hash << 5) - hash)
   }
   return hash
 }
 
 export function intToRGB (i) {
-  var c = (i & 0x00FFFFFF).toString(16).toUpperCase()
+  const c = (i & 0x00FFFFFF).toString(16).toUpperCase()
   return '00000'.substring(0, 6 - c.length) + c
 }
diff --git a/front/src/utils/time.js b/front/src/utils/time.js
index 6c5770c12..7a5f66ccf 100644
--- a/front/src/utils/time.js
+++ b/front/src/utils/time.js
@@ -9,7 +9,7 @@ function pad (val) {
 export default {
   parse: function (sec) {
     let min = 0
-    let hours = Math.floor(sec/3600)
+    const hours = Math.floor(sec / 3600)
     if (hours >= 1) {
       sec = sec % 3600
     }
diff --git a/front/src/utils/url.js b/front/src/utils/url.js
index 61a430988..2055ec675 100644
--- a/front/src/utils/url.js
+++ b/front/src/utils/url.js
@@ -1,7 +1,7 @@
 export default {
   updateQueryString (uri, key, value) {
-    var re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i')
-    var separator = uri.indexOf('?') !== -1 ? '&' : '?'
+    const re = new RegExp('([?&])' + key + '=.*?(&|$)', 'i')
+    const separator = uri.indexOf('?') !== -1 ? '&' : '?'
     if (uri.match(re)) {
       return uri.replace(re, '$1' + key + '=' + value + '$2')
     } else {
diff --git a/front/src/views/Notifications.vue b/front/src/views/Notifications.vue
index eee0a5a02..b97bedc71 100644
--- a/front/src/views/Notifications.vue
+++ b/front/src/views/Notifications.vue
@@ -1,65 +1,151 @@
 <template>
-  <main class="main pusher page-notifications" v-title="labels.title">
+  <main
+    v-title="labels.title"
+    class="main pusher page-notifications"
+  >
     <section class="ui vertical aligned stripe segment">
       <div class="ui container">
-        <div class="ui container" v-if="additionalNotifications">
-          <h1 class="ui header"><translate translate-context="Content/Notifications/Title">Your messages</translate></h1>
+        <div
+          v-if="additionalNotifications"
+          class="ui container"
+        >
+          <h1 class="ui header">
+            <translate translate-context="Content/Notifications/Title">
+              Your messages
+            </translate>
+          </h1>
           <div class="ui two column stackable grid">
-            <div class="column" v-if="showInstanceSupportMessage">
+            <div
+              v-if="showInstanceSupportMessage"
+              class="column"
+            >
               <div class="ui attached info message">
                 <h4 class="header">
-                  <translate translate-context="Content/Notifications/Header">Support this Funkwhale pod</translate>
+                  <translate translate-context="Content/Notifications/Header">
+                    Support this Funkwhale pod
+                  </translate>
                 </h4>
-                <div v-html="markdown.makeHtml($store.state.instance.settings.instance.support_message.value)"></div>
+                <div v-html="markdown.makeHtml($store.state.instance.settings.instance.support_message.value)" />
               </div>
               <div class="ui bottom attached segment">
-                <form @submit.prevent="setDisplayDate('instance_support_message_display_date', instanceSupportMessageDelay)" class="ui inline form">
+                <form
+                  class="ui inline form"
+                  @submit.prevent="setDisplayDate('instance_support_message_display_date', instanceSupportMessageDelay)"
+                >
                   <div class="inline field">
                     <label for="instance-reminder-delay">
                       <translate translate-context="Content/Notifications/Label">Remind me in:</translate>
                     </label>
-                    <select id="instance-reminder-delay" v-model="instanceSupportMessageDelay">
-                      <option :value="30"><translate translate-context="*/*/*">30 days</translate></option>
-                      <option :value="60"><translate translate-context="*/*/*">60 days</translate></option>
-                      <option :value="90"><translate translate-context="*/*/*">90 days</translate></option>
-                      <option :value="null"><translate translate-context="*/*/*">Never</translate></option>
+                    <select
+                      id="instance-reminder-delay"
+                      v-model="instanceSupportMessageDelay"
+                    >
+                      <option :value="30">
+                        <translate translate-context="*/*/*">
+                          30 days
+                        </translate>
+                      </option>
+                      <option :value="60">
+                        <translate translate-context="*/*/*">
+                          60 days
+                        </translate>
+                      </option>
+                      <option :value="90">
+                        <translate translate-context="*/*/*">
+                          90 days
+                        </translate>
+                      </option>
+                      <option :value="null">
+                        <translate translate-context="*/*/*">
+                          Never
+                        </translate>
+                      </option>
                     </select>
-                    <button type="submit" class="ui right floated basic button">
-                      <translate translate-context="Content/Notifications/Button.Label">Got it!</translate>
+                    <button
+                      type="submit"
+                      class="ui right floated basic button"
+                    >
+                      <translate translate-context="Content/Notifications/Button.Label">
+                        Got it!
+                      </translate>
                     </button>
                   </div>
                 </form>
               </div>
             </div>
-            <div class="column" v-if="showFunkwhaleSupportMessage">
+            <div
+              v-if="showFunkwhaleSupportMessage"
+              class="column"
+            >
               <div class="ui info attached message">
                 <h4 class="header">
-                  <translate translate-context="Content/Notifications/Header">Do you like Funkwhale?</translate>
+                  <translate translate-context="Content/Notifications/Header">
+                    Do you like Funkwhale?
+                  </translate>
                 </h4>
                 <p>
-                  <translate translate-context="Content/Notifications/Paragraph">We noticed you've been here for a while. If Funkwhale is useful to you, we could use your help to make it even better!</translate>
+                  <translate translate-context="Content/Notifications/Paragraph">
+                    We noticed you've been here for a while. If Funkwhale is useful to you, we could use your help to make it even better!
+                  </translate>
                 </p>
-                <a href="https://funkwhale.audio/support-us" target="_blank" rel="noopener" class="ui primary inverted button">
+                <a
+                  href="https://funkwhale.audio/support-us"
+                  target="_blank"
+                  rel="noopener"
+                  class="ui primary inverted button"
+                >
                   <translate translate-context="Content/Notifications/Button.Label/Verb">Donate</translate>
                 </a>
-                <a href="https://contribute.funkwhale.audio" target="_blank" rel="noopener" class="ui secondary inverted button">
+                <a
+                  href="https://contribute.funkwhale.audio"
+                  target="_blank"
+                  rel="noopener"
+                  class="ui secondary inverted button"
+                >
                   <translate translate-context="Content/Notifications/Button.Label/Verb">Discover other ways to help</translate>
                 </a>
               </div>
               <div class="ui bottom attached segment">
-                <form @submit.prevent="setDisplayDate('funkwhale_support_message_display_date', funkwhaleSupportMessageDelay)" class="ui inline form">
+                <form
+                  class="ui inline form"
+                  @submit.prevent="setDisplayDate('funkwhale_support_message_display_date', funkwhaleSupportMessageDelay)"
+                >
                   <div class="inline field">
                     <label for="funkwhale-reminder-delay">
                       <translate translate-context="Content/Notifications/Label">Remind me in:</translate>
                     </label>
-                    <select id="funkwhale-reminder-delay" v-model="funkwhaleSupportMessageDelay">
-                      <option :value="30"><translate translate-context="*/*/*">30 days</translate></option>
-                      <option :value="60"><translate translate-context="*/*/*">60 days</translate></option>
-                      <option :value="90"><translate translate-context="*/*/*">90 days</translate></option>
-                      <option :value="null"><translate translate-context="*/*/*">Never</translate></option>
+                    <select
+                      id="funkwhale-reminder-delay"
+                      v-model="funkwhaleSupportMessageDelay"
+                    >
+                      <option :value="30">
+                        <translate translate-context="*/*/*">
+                          30 days
+                        </translate>
+                      </option>
+                      <option :value="60">
+                        <translate translate-context="*/*/*">
+                          60 days
+                        </translate>
+                      </option>
+                      <option :value="90">
+                        <translate translate-context="*/*/*">
+                          90 days
+                        </translate>
+                      </option>
+                      <option :value="null">
+                        <translate translate-context="*/*/*">
+                          Never
+                        </translate>
+                      </option>
                     </select>
-                    <button type="submit" class="ui right floated basic button">
-                      <translate translate-context="Content/Notifications/Button.Label">Got it!</translate>
+                    <button
+                      type="submit"
+                      class="ui right floated basic button"
+                    >
+                      <translate translate-context="Content/Notifications/Button.Label">
+                        Got it!
+                      </translate>
                     </button>
                   </div>
                 </form>
@@ -67,31 +153,58 @@
             </div>
           </div>
         </div>
-        <h1 class="ui header"><translate translate-context="Content/Notifications/Title">Your notifications</translate></h1>
+        <h1 class="ui header">
+          <translate translate-context="Content/Notifications/Title">
+            Your notifications
+          </translate>
+        </h1>
         <div class="ui toggle checkbox">
-          <input id="show-read-notifications" v-model="filters.is_read" type="checkbox">
+          <input
+            id="show-read-notifications"
+            v-model="filters.is_read"
+            type="checkbox"
+          >
           <label for="show-read-notifications"><translate translate-context="Content/Notifications/Form.Label/Verb">Show read notifications</translate></label>
         </div>
         <button
           v-if="filters.is_read === false && notifications.count > 0"
+          class="ui basic labeled icon right floated button"
           @click.prevent="markAllAsRead"
-          class="ui basic labeled icon right floated button">
+        >
           <i class="ui check icon" />
-          <translate translate-context="Content/Notifications/Button.Label/Verb">Mark all as read</translate>
+          <translate translate-context="Content/Notifications/Button.Label/Verb">
+            Mark all as read
+          </translate>
         </button>
         <div class="ui hidden divider" />
 
-        <div v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']">
-          <div class="ui text loader"><translate translate-context="Content/Notifications/Paragraph">Loading notifications…</translate></div>
+        <div
+          v-if="isLoading"
+          :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"
+        >
+          <div class="ui text loader">
+            <translate translate-context="Content/Notifications/Paragraph">
+              Loading notifications…
+            </translate>
+          </div>
         </div>
 
-        <table v-else-if="notifications.count > 0" class="ui table">
+        <table
+          v-else-if="notifications.count > 0"
+          class="ui table"
+        >
           <tbody>
-            <notification-row :item="item" v-for="item in notifications.results" :key="item.id" />
+            <notification-row
+              v-for="item in notifications.results"
+              :key="item.id"
+              :item="item"
+            />
           </tbody>
         </table>
         <p v-else-if="additionalNotifications === 0">
-          <translate translate-context="Content/Notifications/Paragraph">No notification to show.</translate>
+          <translate translate-context="Content/Notifications/Paragraph">
+            No notification to show.
+          </translate>
         </p>
       </div>
     </section>
@@ -99,20 +212,22 @@
 </template>
 
 <script>
-import { mapState, mapGetters } from "vuex"
-import axios from "axios"
-import logger from "@/logging"
+import { mapState, mapGetters } from 'vuex'
+import axios from 'axios'
 import showdown from 'showdown'
 import moment from 'moment'
 
-import NotificationRow from "@/components/notifications/NotificationRow"
+import NotificationRow from '@/components/notifications/NotificationRow'
 
 export default {
-  data() {
+  components: {
+    NotificationRow
+  },
+  data () {
     return {
       isLoading: false,
       markdown: new showdown.Converter(),
-      notifications: {count: 0, results: []},
+      notifications: { count: 0, results: [] },
       instanceSupportMessageDelay: 60,
       funkwhaleSupportMessageDelay: 60,
       filters: {
@@ -120,23 +235,6 @@ export default {
       }
     }
   },
-  components: {
-    NotificationRow
-  },
-  created() {
-    this.fetch(this.filters)
-    this.$store.commit("ui/addWebsocketEventHandler", {
-      eventName: "inbox.item_added",
-      id: "notificationPage",
-      handler: this.handleNewNotification
-    })
-  },
-  destroyed() {
-    this.$store.commit("ui/removeWebsocketEventHandler", {
-      eventName: "inbox.item_added",
-      id: "notificationPage"
-    })
-  },
   computed: {
     ...mapState({
       events: state => state.instance.events
@@ -144,64 +242,78 @@ export default {
     ...mapGetters({
       additionalNotifications: 'ui/additionalNotifications',
       showInstanceSupportMessage: 'ui/showInstanceSupportMessage',
-      showFunkwhaleSupportMessage: 'ui/showFunkwhaleSupportMessage',
+      showFunkwhaleSupportMessage: 'ui/showFunkwhaleSupportMessage'
     }),
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext('*/Notifications/*', "Notifications")
+        title: this.$pgettext('*/Notifications/*', 'Notifications')
       }
     }
   },
+  watch: {
+    'filters.is_read' () {
+      this.fetch(this.filters)
+    }
+  },
+  created () {
+    this.fetch(this.filters)
+    this.$store.commit('ui/addWebsocketEventHandler', {
+      eventName: 'inbox.item_added',
+      id: 'notificationPage',
+      handler: this.handleNewNotification
+    })
+  },
+  destroyed () {
+    this.$store.commit('ui/removeWebsocketEventHandler', {
+      eventName: 'inbox.item_added',
+      id: 'notificationPage'
+    })
+  },
   methods: {
     handleNewNotification (event) {
       this.notifications.count += 1
       this.notifications.results.unshift(event.item)
     },
     setDisplayDate (field, days) {
-      let payload = {}
+      const payload = {}
       let newDisplayDate
       if (days) {
-        newDisplayDate = moment().add({days})
+        newDisplayDate = moment().add({ days })
       } else {
         newDisplayDate = null
       }
       payload[field] = newDisplayDate
-      let self = this
+      const self = this
       axios.patch(`users/${this.$store.state.auth.username}/`, payload).then((response) => {
         self.$store.commit('auth/profilePartialUpdate', response.data)
       })
     },
-    fetch(params) {
+    fetch (params) {
       this.isLoading = true
-      let self = this
-      axios.get("federation/inbox/", { params: params }).then(response => {
+      const self = this
+      axios.get('federation/inbox/', { params: params }).then(response => {
         self.isLoading = false
         self.notifications = response.data
       })
     },
-    markAllAsRead() {
-      let self = this
-      let before = this.notifications.results[0].id
-      let payload = {
-        action: "read",
-        objects: "all",
+    markAllAsRead () {
+      const self = this
+      const before = this.notifications.results[0].id
+      const payload = {
+        action: 'read',
+        objects: 'all',
         filters: {
           is_read: false,
           before
         }
       }
-      axios.post("federation/inbox/action/", payload).then(response => {
-        self.$store.commit("ui/notifications", { type: "inbox", count: 0 })
+      axios.post('federation/inbox/action/', payload).then(response => {
+        self.$store.commit('ui/notifications', { type: 'inbox', count: 0 })
         self.notifications.results.forEach(n => {
           n.is_read = true
         })
       })
     }
-  },
-  watch: {
-    "filters.is_read"() {
-      this.fetch(this.filters)
-    }
   }
 }
 </script>
diff --git a/front/src/views/Search.vue b/front/src/views/Search.vue
index e1a1fd43f..5fb6820ed 100644
--- a/front/src/views/Search.vue
+++ b/front/src/views/Search.vue
@@ -1,83 +1,136 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
     <section class="ui vertical stripe segment">
-      <div class="ui small text container" v-if="initialId">
+      <div
+        v-if="initialId"
+        class="ui small text container"
+      >
         <h2>{{ labels.title }}</h2>
-        <remote-search-form :initial-id="initialId" :type="initialType"></remote-search-form>
+        <remote-search-form
+          :initial-id="initialId"
+          :type="initialType"
+        />
       </div>
-      <div class="ui container" v-else>
+      <div
+        v-else
+        class="ui container"
+      >
         <h2>
           <label for="query">
             <translate translate-context="Content/Search/Input.Label/Noun">Search</translate>
           </label>
         </h2>
-        <form class="ui form" @submit.prevent="page = 1; search()">
+        <form
+          class="ui form"
+          @submit.prevent="page = 1; search()"
+        >
           <div class="ui field">
             <div class="ui action input">
-              <input class="ui input" id="query" name="query" type="text" v-model="query">
-              <button :aria-label="labels.submitSearch" type="submit" class="ui icon button">
-                <i class="search icon"></i>
+              <input
+                id="query"
+                v-model="query"
+                class="ui input"
+                name="query"
+                type="text"
+              >
+              <button
+                :aria-label="labels.submitSearch"
+                type="submit"
+                class="ui icon button"
+              >
+                <i class="search icon" />
               </button>
             </div>
           </div>
         </form>
         <div class="ui secondary pointing menu">
           <a
-            :class="['item', {active: type === t.id}]" 
-            @click.prevent="type = t.id"
             v-for="t in types"
+            :key="t.id"
+            :class="['item', {active: type === t.id}]"
             href=""
-            :key="t.id">
+            @click.prevent="type = t.id"
+          >
             {{ t.label }}
             <span
               v-if="results[t.id]"
-              class="ui circular mini right floated label">
+              class="ui circular mini right floated label"
+            >
               {{ results[t.id].count }}</span>
-            </a>
+          </a>
         </div>
-        <div v-if="isLoading" >
-          <div v-if="isLoading" class="ui inverted active dimmer">
-            <div class="ui loader"></div>
+        <div v-if="isLoading">
+          <div
+            v-if="isLoading"
+            class="ui inverted active dimmer"
+          >
+            <div class="ui loader" />
           </div>
         </div>
-    
-        <empty-state v-else-if="!currentResults || currentResults.count === 0" @refresh="search" :refresh="true"></empty-state>
-        
-        <div v-else-if="type === 'artists' || type === 'podcasts'" class="ui five app-cards cards">
-          <artist-card :artist="artist" v-for="artist in currentResults.results" :key="artist.id"></artist-card>
+
+        <empty-state
+          v-else-if="!currentResults || currentResults.count === 0"
+          :refresh="true"
+          @refresh="search"
+        />
+
+        <div
+          v-else-if="type === 'artists' || type === 'podcasts'"
+          class="ui five app-cards cards"
+        >
+          <artist-card
+            v-for="artist in currentResults.results"
+            :key="artist.id"
+            :artist="artist"
+          />
         </div>
-        
-        <div v-else-if="type === 'albums' || type === 'series'" class="ui five app-cards cards">
+
+        <div
+          v-else-if="type === 'albums' || type === 'series'"
+          class="ui five app-cards cards"
+        >
           <album-card
             v-for="album in currentResults.results"
             :key="album.id"
-            :album="album"></album-card>        
+            :album="album"
+          />
         </div>
-        <track-table v-else-if="type === 'tracks'" :tracks="currentResults.results"></track-table>    
-        <playlist-card-list v-else-if="type === 'playlists'"  :playlists="currentResults.results"></playlist-card-list>
+        <track-table
+          v-else-if="type === 'tracks'"
+          :tracks="currentResults.results"
+        />
+        <playlist-card-list
+          v-else-if="type === 'playlists'"
+          :playlists="currentResults.results"
+        />
         <div
           v-else-if="type === 'radios'"
-          class="ui cards">
+          class="ui cards"
+        >
           <radio-card
-            type="custom"
             v-for="radio in currentResults.results"
             :key="radio.id"
-            :custom-radio="radio"></radio-card>
+            type="custom"
+            :custom-radio="radio"
+          />
         </div>
         <tags-list
           v-else-if="type === 'tags'"
           :truncate-size="200"
           :limit="paginateBy"
-          :tags="currentResults.results.map(t => {return t.name })"></tags-list>
-      
+          :tags="currentResults.results.map(t => {return t.name })"
+        />
+
         <pagination
           v-if="currentResults && currentResults.count > paginateBy"
-          @page-changed="page = $event"
           :current="page"
           :paginate-by="paginateBy"
           :total="currentResults.count"
-          ></pagination>
-      
+          @page-changed="page = $event"
+        />
       </div>
     </section>
   </main>
@@ -85,23 +138,17 @@
 
 <script>
 import RemoteSearchForm from '@/components/RemoteSearchForm'
-import ArtistCard from "@/components/audio/artist/Card"
-import AlbumCard from "@/components/audio/album/Card"
-import TrackTable from "@/components/audio/track/Table"
+import ArtistCard from '@/components/audio/artist/Card'
+import AlbumCard from '@/components/audio/album/Card'
+import TrackTable from '@/components/audio/track/Table'
 import Pagination from '@/components/Pagination'
-import PlaylistCardList from "@/components/playlists/CardList"
-import RadioCard from "@/components/radios/Card"
-import TagsList from "@/components/tags/List"
+import PlaylistCardList from '@/components/playlists/CardList'
+import RadioCard from '@/components/radios/Card'
+import TagsList from '@/components/tags/List'
 
 import axios from 'axios'
 
 export default {
-  props: {
-    initialId: { type: String, required: false},
-    initialType: { type: String, required: false},
-    initialQuery: { type: String, required: false},
-    initialPage: { type: Number, required: false},
-  },
   components: {
     RemoteSearchForm,
     ArtistCard,
@@ -110,7 +157,13 @@ export default {
     Pagination,
     PlaylistCardList,
     RadioCard,
-    TagsList,
+    TagsList
+  },
+  props: {
+    initialId: { type: String, required: false, default: '' },
+    initialType: { type: String, required: false, default: '' },
+    initialQuery: { type: String, required: false, default: '' },
+    initialPage: { type: Number, required: false, default: 0 }
   },
   data () {
     return {
@@ -125,84 +178,81 @@ export default {
         radios: null,
         tags: null,
         podcasts: null,
-        series: null,
+        series: null
       },
       isLoading: false,
-      paginateBy: 25,
+      paginateBy: 25
     }
   },
-  created () {
-    this.search()
-  },
   computed: {
-    labels() {
-      let submitSearch = this.$pgettext("Content/Search/Button.Label/Verb", "Submit Search Query")
-      let title = this.$pgettext("Content/Search/Input.Label/Noun", "Search")
+    labels () {
+      const submitSearch = this.$pgettext('Content/Search/Button.Label/Verb', 'Submit Search Query')
+      let title = this.$pgettext('Content/Search/Input.Label/Noun', 'Search')
       if (this.initialId) {
-        title = this.$pgettext('Head/Fetch/Title', "Search a remote object")
-        if (this.type === "rss") {
-          title = this.$pgettext('Head/Fetch/Title', "Subscribe to a podcast RSS feed")
+        title = this.$pgettext('Head/Fetch/Title', 'Search a remote object')
+        if (this.type === 'rss') {
+          title = this.$pgettext('Head/Fetch/Title', 'Subscribe to a podcast RSS feed')
         }
-      } 
+      }
       return {
         title,
         submitSearch
       }
     },
-    axiosParams() {
-      const params = new URLSearchParams();
-      params.append('q', this.query);
-      params.append('page', this.page);
-      params.append('page_size', this.paginateBy);
-      if(this.currentType.contentCategory != undefined) {params.append('content_category', this.currentType.contentCategory)};
-      if(this.currentType.includeChannels != undefined) {params.append('include_channels', this.currentType.includeChannels)};
-      return params;
+    axiosParams () {
+      const params = new URLSearchParams()
+      params.append('q', this.query)
+      params.append('page', this.page)
+      params.append('page_size', this.paginateBy)
+      if (this.currentType.contentCategory !== undefined) { params.append('content_category', this.currentType.contentCategory) };
+      if (this.currentType.includeChannels !== undefined) { params.append('include_channels', this.currentType.includeChannels) };
+      return params
     },
     types () {
       return [
         {
           id: 'artists',
-          label: this.$pgettext("*/*/*/Noun", "Artists"),
+          label: this.$pgettext('*/*/*/Noun', 'Artists'),
           includeChannels: true,
-          contentCategory: 'music',
+          contentCategory: 'music'
         },
         {
           id: 'albums',
-          label: this.$pgettext("*/*/*", "Albums"),
+          label: this.$pgettext('*/*/*', 'Albums'),
           includeChannels: true,
-          contentCategory: 'music',
+          contentCategory: 'music'
         },
         {
           id: 'tracks',
-          label: this.$pgettext("*/*/*", "Tracks"),
+          label: this.$pgettext('*/*/*', 'Tracks')
         },
         {
           id: 'playlists',
-          label: this.$pgettext("*/*/*", "Playlists"),
+          label: this.$pgettext('*/*/*', 'Playlists')
         },
         {
           id: 'radios',
-          label: this.$pgettext("*/*/*", "Radios"),
-          endpoint: 'radios/radios',
+          label: this.$pgettext('*/*/*', 'Radios'),
+          endpoint: 'radios/radios'
         },
         {
           id: 'tags',
-          label: this.$pgettext("*/*/*", "Tags"),
+          label: this.$pgettext('*/*/*', 'Tags')
         },
         {
           id: 'podcasts',
-          label: this.$pgettext("*/*/*", "Podcasts"),
+          label: this.$pgettext('*/*/*', 'Podcasts'),
           endpoint: '/artists',
           contentCategory: 'podcast',
-          includeChannels: true,
+          includeChannels: true
         },
         {
           id: 'series',
-          label: this.$pgettext("*/*/*", "Series"),
+          label: this.$pgettext('*/*/*', 'Series'),
           endpoint: '/albums',
           includeChannels: true,
-          contentCategory: 'podcast',
-        },
+          contentCategory: 'podcast'
+        }
       ]
     },
     currentType () {
@@ -214,6 +264,25 @@ export default {
       return this.results[this.currentType.id]
     }
   },
+  watch: {
+    async type () {
+      this.page = 1
+      this.updateQueryString()
+      await this.search()
+    },
+    async page () {
+      this.updateQueryString()
+      await this.search()
+    },
+    '$route.query.q': async function (v) {
+      this.query = v
+      this.updateQueryString()
+      await this.search()
+    }
+  },
+  created () {
+    this.search()
+  },
   methods: {
     async search () {
       this.updateQueryString()
@@ -224,52 +293,38 @@ export default {
         return
       }
       this.isLoading = true
-      let response = await axios.get(
+      const response = await axios.get(
         this.currentType.endpoint || this.currentType.id,
-        {params: this.axiosParams}
+        { params: this.axiosParams }
       )
       this.results[this.currentType.id] = response.data
       this.isLoading = false
       this.types.forEach(t => {
-        if (t.id != this.currentType.id) {
-          axios.get(t.endpoint || t.id, {params: {
-              q: this.query, 
+        if (t.id !== this.currentType.id) {
+          axios.get(t.endpoint || t.id, {
+            params: {
+              q: this.query,
               page_size: 1,
               content_category: t.contentCategory,
-              include_channels: t.includeChannels,
-            }}).then(response => {
+              include_channels: t.includeChannels
+            }
+          }).then(response => {
             this.results[t.id] = response.data
           })
         }
       })
     },
-    updateQueryString: function() {
+    updateQueryString: function () {
       history.pushState(
         {},
         null,
         this.$route.path + '?' + new URLSearchParams(
           {
-          q: this.query,
-          page: this.page,
-          type: this.type,
-        }).toString()
+            q: this.query,
+            page: this.page,
+            type: this.type
+          }).toString()
       )
-    },
-  },
-  watch: {
-    async type () {
-      this.page = 1
-      this.updateQueryString()
-      await this.search()
-    },
-    async page () {
-      this.updateQueryString()
-      await this.search()
-    },
-    "$route.query.q": async function (v) {
-      this.query = v
-      this.updateQueryString()
-      await this.search()
     }
   }
 }
diff --git a/front/src/views/admin/ChannelDetail.vue b/front/src/views/admin/ChannelDetail.vue
index 3b9234785..4f83947fe 100644
--- a/front/src/views/admin/ChannelDetail.vue
+++ b/front/src/views/admin/ChannelDetail.vue
@@ -1,22 +1,36 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object">
-      <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="object.artist.name">
+      <section
+        v-title="object.artist.name"
+        :class="['ui', 'head', 'vertical', 'stripe', 'segment']"
+      >
         <div class="ui stackable one column grid">
           <div class="ui column">
             <div class="segment-content">
               <h2 class="ui header">
-                <img alt="" v-if="object.artist.cover && object.artist.cover.urls.medium_square_crop" v-lazy="$store.getters['instance/absoluteUrl'](object.artist.cover.urls.medium_square_crop)">
-                <img alt="" v-else src="../../assets/audio/default-cover.png">
+                <img
+                  v-if="object.artist.cover && object.artist.cover.urls.medium_square_crop"
+                  v-lazy="$store.getters['instance/absoluteUrl'](object.artist.cover.urls.medium_square_crop)"
+                  alt=""
+                >
+                <img
+                  v-else
+                  alt=""
+                  src="../../assets/audio/default-cover.png"
+                >
                 <div class="content">
                   {{ object.artist.name | truncate(100) }}
                   <div class="sub header">
                     <template v-if="object.artist.is_local">
                       <span class="ui tiny accent label">
-                        <i class="home icon"></i>
+                        <i class="home icon" />
                         <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
                       </span>
                       &nbsp;
@@ -25,34 +39,59 @@
                 </div>
               </h2>
               <template v-if="object.artist.tags && object.artist.tags.length > 0">
-                <tags-list :limit="5" detail-route="manage.library.tags.detail" :tags="object.artist.tags"></tags-list>
-                <div class="ui hidden divider"></div>
+                <tags-list
+                  :limit="5"
+                  detail-route="manage.library.tags.detail"
+                  :tags="object.artist.tags"
+                />
+                <div class="ui hidden divider" />
               </template>
 
               <div class="header-buttons">
-
                 <div class="ui icon buttons">
-                  <router-link class="ui labeled icon button" :to="{name: 'channels.detail', params: {id: object.uuid }}">
-                    <i class="info icon"></i>
-                    <translate translate-context="Content/Moderation/Link/Verb">Open local profile</translate>&nbsp;
+                  <router-link
+                    class="ui labeled icon button"
+                    :to="{name: 'channels.detail', params: {id: object.uuid }}"
+                  >
+                    <i class="info icon" />
+                    <translate translate-context="Content/Moderation/Link/Verb">
+                      Open local profile
+                    </translate>&nbsp;
                   </router-link>
-                  <button class="ui floating dropdown icon button" v-dropdown>
-                    <i class="dropdown icon"></i>
+                  <button
+                    v-dropdown
+                    class="ui floating dropdown icon button"
+                  >
+                    <i class="dropdown icon" />
                     <div class="menu">
                       <a
                         v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                         class="basic item"
                         :href="$store.getters['instance/absoluteUrl'](`/api/admin/audio/channel/${object.id}`)"
-                        target="_blank" rel="noopener noreferrer">
-                        <i class="wrench icon"></i>
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="wrench icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                       </a>
-                      <fetch-button @refresh="fetchData" v-if="!object.actor.is_local" class="basic item" :url="`channels/${object.uuid}/fetches/`">
-                        <i class="refresh icon"></i>&nbsp;
-                        <translate translate-context="Content/Moderation/Button/Verb">Refresh from remote server</translate>&nbsp;
+                      <fetch-button
+                        v-if="!object.actor.is_local"
+                        class="basic item"
+                        :url="`channels/${object.uuid}/fetches/`"
+                        @refresh="fetchData"
+                      >
+                        <i class="refresh icon" />&nbsp;
+                        <translate translate-context="Content/Moderation/Button/Verb">
+                          Refresh from remote server
+                        </translate>&nbsp;
                       </fetch-button>
-                      <a class="basic item" :href="object.actor.url || object.actor.fid" target="_blank" rel="noopener noreferrer">
-                        <i class="external icon"></i>
+                      <a
+                        class="basic item"
+                        :href="object.actor.url || object.actor.fid"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="external icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate>&nbsp;
                       </a>
                     </div>
@@ -61,13 +100,28 @@
                 <div class="ui buttons">
                   <dangerous-button
                     :class="['ui', {loading: isLoading}, 'basic danger button']"
-                    :action="remove">
-                    <translate translate-context="*/*/*/Verb">Delete</translate>
-                    <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this channel?</translate></p>
+                    :action="remove"
+                  >
+                    <translate translate-context="*/*/*/Verb">
+                      Delete
+                    </translate>
+                    <p slot="modal-header">
+                      <translate translate-context="Popup/Library/Title">
+                        Delete this channel?
+                      </translate>
+                    </p>
                     <div slot="modal-content">
-                      <p><translate translate-context="Content/Moderation/Paragraph">The channel will be removed, as well as associated uploads, tracks, and albums. This action is irreversible.</translate></p>
+                      <p>
+                        <translate translate-context="Content/Moderation/Paragraph">
+                          The channel will be removed, as well as associated uploads, tracks, and albums. This action is irreversible.
+                        </translate>
+                      </p>
                     </div>
-                    <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p>
+                    <p slot="modal-confirm">
+                      <translate translate-context="*/*/*/Verb">
+                        Delete
+                      </translate>
+                    </p>
                   </dangerous-button>
                 </div>
               </div>
@@ -80,16 +134,20 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="info icon"></i>
+                <i class="info icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Channel data</translate>
+                  <translate translate-context="Content/Moderation/Title">
+                    Channel data
+                  </translate>
                 </div>
               </h3>
               <table class="ui very basic table">
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Name</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Name
+                      </translate>
                     </td>
                     <td>
                       {{ object.artist.name }}
@@ -98,7 +156,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.channels', query: {q: getQuery('category', object.artist.content_category) }}">
-                        <translate translate-context="*/*/*">Category</translate>
+                        <translate translate-context="*/*/*">
+                          Category
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -108,7 +168,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: object.attributed_to.full_username }}">
-                        <translate translate-context="*/*/*/Noun">Account</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Account
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -118,7 +180,9 @@
                   <tr v-if="!object.actor.is_local">
                     <td>
                       <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.actor.domain }}">
-                        <translate translate-context="Content/Moderation/*/Noun">Domain</translate>
+                        <translate translate-context="Content/Moderation/*/Noun">
+                          Domain
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -127,24 +191,38 @@
                   </tr>
                   <tr v-if="object.artist.description">
                     <td>
-                      <translate translate-context="'*/*/*/Noun">Description</translate>
+                      <translate translate-context="'*/*/*/Noun">
+                        Description
+                      </translate>
                     </td>
-                    <td v-html="object.artist.description.html"></td>
+                    <td v-html="object.artist.description.html" />
                   </tr>
                   <tr v-if="object.actor.url">
                     <td>
-                      <translate translate-context="'Content/*/*/Noun">URL</translate>
+                      <translate translate-context="'Content/*/*/Noun">
+                        URL
+                      </translate>
                     </td>
                     <td>
-                      <a :href="object.actor.url" rel="noreferrer noopener" target="_blank">{{ object.actor.url }}</a>
+                      <a
+                        :href="object.actor.url"
+                        rel="noreferrer noopener"
+                        target="_blank"
+                      >{{ object.actor.url }}</a>
                     </td>
                   </tr>
                   <tr v-if="object.rss_url">
                     <td>
-                      <translate translate-context="'*/*/*">RSS Feed</translate>
+                      <translate translate-context="'*/*/*">
+                        RSS Feed
+                      </translate>
                     </td>
                     <td>
-                      <a :href="object.rss_url" rel="noreferrer noopener" target="_blank">{{ object.rss_url }}</a>
+                      <a
+                        :href="object.rss_url"
+                        rel="noreferrer noopener"
+                        target="_blank"
+                      >{{ object.rss_url }}</a>
                     </td>
                   </tr>
                 </tbody>
@@ -154,32 +232,43 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="feed icon"></i>
+                <i class="feed icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Activity</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Activity
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">
+                        First seen
+                      </translate>
                     </td>
                     <td>
-                      <human-date :date="object.creation_date"></human-date>
+                      <human-date :date="object.creation_date" />
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Listenings</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Listenings
+                      </translate>
                     </td>
                     <td>
                       {{ stats.listenings }}
@@ -187,7 +276,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*">Favorited tracks</translate>
+                      <translate translate-context="*/*/*">
+                        Favorited tracks
+                      </translate>
                     </td>
                     <td>
                       {{ stats.track_favorites }}
@@ -195,7 +286,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*">Playlists</translate>
+                      <translate translate-context="*/*/*">
+                        Playlists
+                      </translate>
                     </td>
                     <td>
                       {{ stats.playlists }}
@@ -204,7 +297,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `channel:${object.uuid}`) }}">
-                        <translate translate-context="Content/Moderation/Table.Label/Noun">Linked reports</translate>
+                        <translate translate-context="Content/Moderation/Table.Label/Noun">
+                          Linked reports
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -214,7 +309,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.edits', query: {q: getQuery('target', 'artist ' + object.artist.id)}}">
-                        <translate translate-context="*/Admin/*/Noun">Edits</translate>
+                        <translate translate-context="*/Admin/*/Noun">
+                          Edits
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -228,25 +325,33 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="music icon"></i>
+                <i class="music icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Audio content</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Audio content
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
-
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Cached size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_downloaded_size | humanSize }}
@@ -254,7 +359,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label">Total size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label">
+                        Total size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_total_size | humanSize }}
@@ -263,7 +370,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('channel_id', object.uuid) }}">
-                        <translate translate-context="*/*/*">Uploads</translate>
+                        <translate translate-context="*/*/*">
+                          Uploads
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -273,7 +382,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.albums', query: {q: getQuery('channel_id', object.uuid) }}">
-                        <translate translate-context="*/*/*">Albums</translate>
+                        <translate translate-context="*/*/*">
+                          Albums
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -283,7 +394,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('channel_id', object.uuid) }}">
-                        <translate translate-context="*/*/*">Tracks</translate>
+                        <translate translate-context="*/*/*">
+                          Tracks
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -292,78 +405,75 @@
                   </tr>
                 </tbody>
               </table>
-
             </section>
           </div>
         </div>
       </div>
-
     </template>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import logger from "@/logging"
+import axios from 'axios'
 
-import TagsList from "@/components/tags/List"
-import FetchButton from "@/components/federation/FetchButton"
+import TagsList from '@/components/tags/List'
+import FetchButton from '@/components/federation/FetchButton'
 
 export default {
-  props: ["id"],
   components: {
     FetchButton,
     TagsList
   },
-  data() {
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       isLoading: true,
       isLoadingStats: false,
       object: null,
-      stats: null,
+      stats: null
     }
   },
-  created() {
+  computed: {
+    labels () {
+      return {
+        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
+      }
+    }
+  },
+  created () {
     this.fetchData()
     this.fetchStats()
   },
   methods: {
-    fetchData() {
-      var self = this
+    fetchData () {
+      const self = this
       this.isLoading = true
-      let url = `manage/channels/${this.id}/`
+      const url = `manage/channels/${this.id}/`
       axios.get(url).then(response => {
         self.object = response.data
         self.isLoading = false
       })
     },
-    fetchStats() {
-      var self = this
+    fetchStats () {
+      const self = this
       this.isLoadingStats = true
-      let url = `manage/channels/${this.id}/stats/`
+      const url = `manage/channels/${this.id}/stats/`
       axios.get(url).then(response => {
         self.stats = response.data
         self.isLoadingStats = false
       })
     },
     remove () {
-      var self = this
+      const self = this
       this.isLoading = true
-      let url = `manage/channels/${this.id}/`
+      const url = `manage/channels/${this.id}/`
       axios.delete(url).then(response => {
-        self.$router.push({name: 'manage.channels'})
+        self.$router.push({ name: 'manage.channels' })
       })
     },
     getQuery (field, value) {
       return `${field}:"${value}"`
     }
-  },
-  computed: {
-    labels() {
-      return {
-        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'),
-      }
-    },
   }
 }
 </script>
diff --git a/front/src/views/admin/ChannelsList.vue b/front/src/views/admin/ChannelsList.vue
index 74cfb56cc..2a86598ef 100644
--- a/front/src/views/admin/ChannelsList.vue
+++ b/front/src/views/admin/ChannelsList.vue
@@ -1,25 +1,30 @@
 <template>
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header">{{ labels.title }}</h2>
-      <div class="ui hidden divider"></div>
-      <channels-table :update-url="true" :default-query="defaultQuery"></channels-table>
+      <h2 class="ui header">
+        {{ labels.title }}
+      </h2>
+      <div class="ui hidden divider" />
+      <channels-table
+        :update-url="true"
+        :default-query="defaultQuery"
+      />
     </section>
   </main>
 </template>
 
 <script>
-import ChannelsTable from "@/components/manage/ChannelsTable"
+import ChannelsTable from '@/components/manage/ChannelsTable'
 
 export default {
   components: {
     ChannelsTable
   },
   props: {
-    defaultQuery: {type: String, required: false},
+    defaultQuery: { type: String, required: false, default: '' }
   },
   computed: {
-    labels() {
+    labels () {
       return {
         title: this.$pgettext('*/*/*', 'Channels')
       }
diff --git a/front/src/views/admin/Settings.vue b/front/src/views/admin/Settings.vue
index 30f79f829..ade695e93 100644
--- a/front/src/views/admin/Settings.vue
+++ b/front/src/views/admin/Settings.vue
@@ -1,203 +1,216 @@
 <template>
-  <main class="main pusher"  v-title="labels.settings">
+  <main
+    v-title="labels.settings"
+    class="main pusher"
+  >
     <div class="ui vertical stripe segment">
       <div class="ui text container">
-        <div :class="['ui', {'loading': isLoading}, 'form']"></div>
-        <div id="settings-grid" v-if="settingsData" class="ui grid">
+        <div :class="['ui', {'loading': isLoading}, 'form']" />
+        <div
+          v-if="settingsData"
+          id="settings-grid"
+          class="ui grid"
+        >
           <div class="twelve wide stretched column">
             <settings-group
+              v-for="group in groups"
+              :key="group.title"
               :settings-data="settingsData"
               :group="group"
-              :key="group.title"
-              v-for="group in groups" />
+            />
           </div>
           <div class="four wide column">
             <div class="ui sticky vertical secondary menu">
-              <div class="header item"><translate translate-context="Content/Admin/Menu.Title">Sections</translate></div>
-              <a :class="['menu', {active: group.id === current}, 'item']"
-                @click.prevent="scrollTo(group.id)"
+              <div class="header item">
+                <translate translate-context="Content/Admin/Menu.Title">
+                  Sections
+                </translate>
+              </div>
+              <a
+                v-for="(group, key) in groups"
+                :key="key"
+                :class="['menu', {active: group.id === current}, 'item']"
                 :href="'#' + group.id"
-                v-for="group in groups">{{ group.label }}</a>
+                @click.prevent="scrollTo(group.id)"
+              >{{ group.label }}</a>
             </div>
           </div>
         </div>
-
       </div>
     </div>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import $ from "jquery"
+import axios from 'axios'
+import $ from 'jquery'
 
-import SettingsGroup from "@/components/admin/SettingsGroup"
+import SettingsGroup from '@/components/admin/SettingsGroup'
 
 export default {
   components: {
     SettingsGroup
   },
-  data() {
+  data () {
     return {
       isLoading: false,
       settingsData: null,
       current: null
     }
   },
-  created() {
-    let self = this
-    this.fetchSettings().then(r => {
-      self.$nextTick(() => {
-        if (self.$store.state.route.hash) {
-          self.scrollTo(self.$store.state.route.hash.substr(1))
-        }
-        $("select.dropdown").dropdown()
-      })
-    })
-  },
-  methods: {
-    scrollTo(id) {
-      this.current = id
-      document.getElementById(id).scrollIntoView()
-    },
-    fetchSettings() {
-      let self = this
-      self.isLoading = true
-      return axios.get("instance/admin/settings/").then(response => {
-        self.settingsData = response.data
-        self.isLoading = false
-      })
-    }
-  },
   computed: {
-    labels() {
+    labels () {
       return {
         settings: this.$pgettext('Head/Admin/Title', 'Instance settings')
       }
     },
-    groups() {
+    groups () {
       // somehow, extraction fails if in the return block directly
-      let instanceLabel = this.$pgettext('Content/Admin/Menu','Instance information')
-      let signupsLabel = this.$pgettext('*/*/*/Noun', 'Sign-ups')
-      let securityLabel = this.$pgettext('*/*/*/Noun', 'Security')
-      let musicLabel = this.$pgettext('*/*/*/Noun', 'Music')
-      let channelsLabel = this.$pgettext('*/*/*', 'Channels')
-      let playlistsLabel = this.$pgettext('*/*/*', 'Playlists')
-      let federationLabel = this.$pgettext('*/*/*', 'Federation')
-      let moderationLabel = this.$pgettext('*/Moderation/*', 'Moderation')
-      let subsonicLabel = this.$pgettext('Content/Admin/Menu', 'Subsonic')
-      let statisticsLabel = this.$pgettext('Content/Home/Header', 'Statistics')
-      let uiLabel = this.$pgettext('Content/Admin/Menu', 'User Interface')
-      let errorLabel = this.$pgettext('Content/Admin/Menu', 'Error reporting')
+      const instanceLabel = this.$pgettext('Content/Admin/Menu', 'Instance information')
+      const signupsLabel = this.$pgettext('*/*/*/Noun', 'Sign-ups')
+      const securityLabel = this.$pgettext('*/*/*/Noun', 'Security')
+      const musicLabel = this.$pgettext('*/*/*/Noun', 'Music')
+      const channelsLabel = this.$pgettext('*/*/*', 'Channels')
+      const playlistsLabel = this.$pgettext('*/*/*', 'Playlists')
+      const federationLabel = this.$pgettext('*/*/*', 'Federation')
+      const moderationLabel = this.$pgettext('*/Moderation/*', 'Moderation')
+      const subsonicLabel = this.$pgettext('Content/Admin/Menu', 'Subsonic')
+      const statisticsLabel = this.$pgettext('Content/Home/Header', 'Statistics')
+      const uiLabel = this.$pgettext('Content/Admin/Menu', 'User Interface')
       return [
         {
           label: instanceLabel,
-          id: "instance",
+          id: 'instance',
           settings: [
-            {name: "instance__name"},
-            {name: "instance__short_description"},
-            {name: "instance__long_description", fieldType: 'markdown', fieldParams: {charLimit: null, permissive: true}},
-            {name: "instance__contact_email"},
-            {name: "instance__rules", fieldType: 'markdown', fieldParams: {charLimit: null, permissive: true}},
-            {name: "instance__terms", fieldType: 'markdown', fieldParams: {charLimit: null, permissive: true}},
-            {name: "instance__banner"},
-            {name: "instance__support_message", fieldType: 'markdown', fieldParams: {charLimit: null, permissive: true}},
+            { name: 'instance__name' },
+            { name: 'instance__short_description' },
+            { name: 'instance__long_description', fieldType: 'markdown', fieldParams: { charLimit: null, permissive: true } },
+            { name: 'instance__contact_email' },
+            { name: 'instance__rules', fieldType: 'markdown', fieldParams: { charLimit: null, permissive: true } },
+            { name: 'instance__terms', fieldType: 'markdown', fieldParams: { charLimit: null, permissive: true } },
+            { name: 'instance__banner' },
+            { name: 'instance__support_message', fieldType: 'markdown', fieldParams: { charLimit: null, permissive: true } }
           ]
         },
         {
           label: signupsLabel,
-          id: "signup",
+          id: 'signup',
           settings: [
-            {name: "users__registration_enabled"},
-            {name: "moderation__signup_approval_enabled"},
-            {name: "moderation__signup_form_customization", fieldType: 'formBuilder'},
+            { name: 'users__registration_enabled' },
+            { name: 'moderation__signup_approval_enabled' },
+            { name: 'moderation__signup_form_customization', fieldType: 'formBuilder' }
           ]
         },
         {
           label: securityLabel,
-          id: "security",
+          id: 'security',
           settings: [
-            {name: "common__api_authentication_required"},
-            {name: "users__default_permissions"},
-            {name: "users__upload_quota"},
+            { name: 'common__api_authentication_required' },
+            { name: 'users__default_permissions' },
+            { name: 'users__upload_quota' }
           ]
         },
         {
           label: musicLabel,
-          id: "music",
+          id: 'music',
           settings: [
-            {name: "music__transcoding_enabled"},
-            {name: "music__transcoding_cache_duration"},
+            { name: 'music__transcoding_enabled' },
+            { name: 'music__transcoding_cache_duration' }
           ]
         },
         {
           label: channelsLabel,
-          id: "channels",
+          id: 'channels',
           settings: [
-            {name: "audio__channels_enabled"},
-            {name: "audio__max_channels"},
+            { name: 'audio__channels_enabled' },
+            { name: 'audio__max_channels' }
           ]
         },
         {
           label: playlistsLabel,
-          id: "playlists",
+          id: 'playlists',
           settings: [
-            {name: "playlists__max_tracks"},
+            { name: 'playlists__max_tracks' }
           ]
         },
         {
           label: moderationLabel,
-          id: "moderation",
+          id: 'moderation',
           settings: [
-            {name: "moderation__allow_list_enabled"},
-            {name: "moderation__allow_list_public"},
-            {name: "moderation__unauthenticated_report_types"},
+            { name: 'moderation__allow_list_enabled' },
+            { name: 'moderation__allow_list_public' },
+            { name: 'moderation__unauthenticated_report_types' }
           ]
         },
         {
           label: federationLabel,
-          id: "federation",
+          id: 'federation',
           settings: [
-            {name: "federation__enabled"},
-            {name: "federation__public_index"},
-            {name: "federation__collection_page_size"},
-            {name: "federation__music_cache_duration"},
-            {name: "federation__actor_fetch_delay"},
+            { name: 'federation__enabled' },
+            { name: 'federation__public_index' },
+            { name: 'federation__collection_page_size' },
+            { name: 'federation__music_cache_duration' },
+            { name: 'federation__actor_fetch_delay' }
           ]
         },
         {
           label: subsonicLabel,
-          id: "subsonic",
+          id: 'subsonic',
           settings: [
-            {name: "subsonic__enabled"},
+            { name: 'subsonic__enabled' }
           ]
         },
         {
           label: uiLabel,
-          id: "ui",
+          id: 'ui',
           settings: [
-            {name: "ui__custom_css"},
-            {name: "instance__funkwhale_support_message_enabled"},
+            { name: 'ui__custom_css' },
+            { name: 'instance__funkwhale_support_message_enabled' }
           ]
         },
         {
           label: statisticsLabel,
-          id: "statistics",
+          id: 'statistics',
           settings: [
-            {name: "instance__nodeinfo_stats_enabled"},
-            {name: "instance__nodeinfo_private"},
+            { name: 'instance__nodeinfo_stats_enabled' },
+            { name: 'instance__nodeinfo_private' }
           ]
         }
       ]
     }
   },
   watch: {
-    settingsData() {
-      let self = this
+    settingsData () {
+      const self = this
       this.$nextTick(() => {
         $(self.$el)
-          .find(".sticky")
-          .sticky({ context: "#settings-grid" })
+          .find('.sticky')
+          .sticky({ context: '#settings-grid' })
+      })
+    }
+  },
+  created () {
+    const self = this
+    this.fetchSettings().then(r => {
+      self.$nextTick(() => {
+        if (self.$store.state.route.hash) {
+          self.scrollTo(self.$store.state.route.hash.substr(1))
+        }
+        $('select.dropdown').dropdown()
+      })
+    })
+  },
+  methods: {
+    scrollTo (id) {
+      this.current = id
+      document.getElementById(id).scrollIntoView()
+    },
+    fetchSettings () {
+      const self = this
+      self.isLoading = true
+      return axios.get('instance/admin/settings/').then(response => {
+        self.settingsData = response.data
+        self.isLoading = false
       })
     }
   }
diff --git a/front/src/views/admin/library/AlbumDetail.vue b/front/src/views/admin/library/AlbumDetail.vue
index 33f8808a4..50cba8da7 100644
--- a/front/src/views/admin/library/AlbumDetail.vue
+++ b/front/src/views/admin/library/AlbumDetail.vue
@@ -1,22 +1,36 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object">
-      <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="object.title">
+      <section
+        v-title="object.title"
+        :class="['ui', 'head', 'vertical', 'stripe', 'segment']"
+      >
         <div class="ui stackable one column grid">
           <div class="ui column">
             <div class="segment-content">
               <h2 class="ui header">
-                <img alt="" v-if="object.cover && object.cover.urls.original" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.medium_square_crop)">
-                <img alt="" v-else src="../../../assets/audio/default-cover.png">
+                <img
+                  v-if="object.cover && object.cover.urls.original"
+                  v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.medium_square_crop)"
+                  alt=""
+                >
+                <img
+                  v-else
+                  alt=""
+                  src="../../../assets/audio/default-cover.png"
+                >
                 <div class="content">
                   {{ object.title | truncate(100) }}
                   <div class="sub header">
                     <template v-if="object.is_local">
                       <span class="ui tiny accent label">
-                        <i class="home icon"></i>
+                        <i class="home icon" />
                         <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
                       </span>
                       &nbsp;
@@ -26,38 +40,69 @@
               </h2>
 
               <template v-if="object.tags && object.tags.length > 0">
-                <tags-list :limit="5" detail-route="manage.library.tags.detail" :tags="object.tags"></tags-list>
-                <div class="ui hidden divider"></div>
+                <tags-list
+                  :limit="5"
+                  detail-route="manage.library.tags.detail"
+                  :tags="object.tags"
+                />
+                <div class="ui hidden divider" />
               </template>
 
               <div class="header-buttons">
-
                 <div class="ui icon buttons">
-                  <router-link class="ui labeled icon button" :to="{name: 'library.albums.detail', params: {id: object.id }}">
-                    <i class="info icon"></i>
-                    <translate translate-context="Content/Moderation/Link/Verb">Open local profile</translate>&nbsp;
+                  <router-link
+                    class="ui labeled icon button"
+                    :to="{name: 'library.albums.detail', params: {id: object.id }}"
+                  >
+                    <i class="info icon" />
+                    <translate translate-context="Content/Moderation/Link/Verb">
+                      Open local profile
+                    </translate>&nbsp;
                   </router-link>
-                  <button class="ui floating dropdown icon button" v-dropdown>
-                    <i class="dropdown icon"></i>
+                  <button
+                    v-dropdown
+                    class="ui floating dropdown icon button"
+                  >
+                    <i class="dropdown icon" />
                     <div class="menu">
                       <a
                         v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                         class="basic item"
                         :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/album/${object.id}`)"
-                        target="_blank" rel="noopener noreferrer">
-                        <i class="wrench icon"></i>
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="wrench icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                       </a>
-                      <a class="basic item" v-if="object.mbid" :href="`https://musicbrainz.org/release/${object.mbid}`" target="_blank" rel="noopener noreferrer">
-                        <i class="external icon"></i>
+                      <a
+                        v-if="object.mbid"
+                        class="basic item"
+                        :href="`https://musicbrainz.org/release/${object.mbid}`"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="external icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">Open on MusicBrainz</translate>&nbsp;
                       </a>
-                      <fetch-button @refresh="fetchData" v-if="!object.is_local" class="basic item" :url="`albums/${object.id}/fetches/`">
-                        <i class="refresh icon"></i>&nbsp;
-                        <translate translate-context="Content/Moderation/Button/Verb">Refresh from remote server</translate>&nbsp;
+                      <fetch-button
+                        v-if="!object.is_local"
+                        class="basic item"
+                        :url="`albums/${object.id}/fetches/`"
+                        @refresh="fetchData"
+                      >
+                        <i class="refresh icon" />&nbsp;
+                        <translate translate-context="Content/Moderation/Button/Verb">
+                          Refresh from remote server
+                        </translate>&nbsp;
                       </fetch-button>
-                      <a class="basic item" :href="object.url || object.fid" target="_blank" rel="noopener noreferrer">
-                        <i class="external icon"></i>
+                      <a
+                        class="basic item"
+                        :href="object.url || object.fid"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="external icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate>&nbsp;
                       </a>
                     </div>
@@ -67,21 +112,39 @@
                   <router-link
                     v-if="object.is_local"
                     :to="{name: 'library.albums.edit', params: {id: object.id }}"
-                    class="ui labeled icon button">
-                    <i class="edit icon"></i>
-                    <translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
+                    class="ui labeled icon button"
+                  >
+                    <i class="edit icon" />
+                    <translate translate-context="Content/*/Button.Label/Verb">
+                      Edit
+                    </translate>
                   </router-link>
                 </div>
                 <div class="ui buttons">
                   <dangerous-button
                     :class="['ui', {loading: isLoading}, 'basic danger button']"
-                    :action="remove">
-                    <translate translate-context="*/*/*/Verb">Delete</translate>
-                    <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this album?</translate></p>
+                    :action="remove"
+                  >
+                    <translate translate-context="*/*/*/Verb">
+                      Delete
+                    </translate>
+                    <p slot="modal-header">
+                      <translate translate-context="Popup/Library/Title">
+                        Delete this album?
+                      </translate>
+                    </p>
                     <div slot="modal-content">
-                      <p><translate translate-context="Content/Moderation/Paragraph">The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible.</translate></p>
+                      <p>
+                        <translate translate-context="Content/Moderation/Paragraph">
+                          The album will be removed, as well as associated uploads, tracks, favorites and listening history. This action is irreversible.
+                        </translate>
+                      </p>
                     </div>
-                    <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p>
+                    <p slot="modal-confirm">
+                      <translate translate-context="*/*/*/Verb">
+                        Delete
+                      </translate>
+                    </p>
                   </dangerous-button>
                 </div>
               </div>
@@ -94,16 +157,20 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="info icon"></i>
+                <i class="info icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Album data</translate>
+                  <translate translate-context="Content/Moderation/Title">
+                    Album data
+                  </translate>
                 </div>
               </h3>
               <table class="ui very basic table">
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Title</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Title
+                      </translate>
                     </td>
                     <td>
                       {{ object.title }}
@@ -112,7 +179,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.artists.detail', params: {id: object.artist.id }}">
-                        <translate translate-context="*/*/*/Noun">Artist</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Artist
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -122,7 +191,9 @@
                   <tr v-if="!object.is_local">
                     <td>
                       <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}">
-                        <translate translate-context="Content/Moderation/*/Noun">Domain</translate>
+                        <translate translate-context="Content/Moderation/*/Noun">
+                          Domain
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -131,9 +202,11 @@
                   </tr>
                   <tr v-if="object.description">
                     <td>
-                      <translate translate-context="'*/*/*/Noun">Description</translate>
+                      <translate translate-context="'*/*/*/Noun">
+                        Description
+                      </translate>
                     </td>
-                    <td v-html="object.description.html"></td>
+                    <td v-html="object.description.html" />
                   </tr>
                 </tbody>
               </table>
@@ -142,32 +215,43 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="feed icon"></i>
+                <i class="feed icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Activity</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Activity
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">
+                        First seen
+                      </translate>
                     </td>
                     <td>
-                      <human-date :date="object.creation_date"></human-date>
+                      <human-date :date="object.creation_date" />
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Listenings</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Listenings
+                      </translate>
                     </td>
                     <td>
                       {{ stats.listenings }}
@@ -175,7 +259,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*">Favorited tracks</translate>
+                      <translate translate-context="*/*/*">
+                        Favorited tracks
+                      </translate>
                     </td>
                     <td>
                       {{ stats.track_favorites }}
@@ -183,7 +269,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*">Playlists</translate>
+                      <translate translate-context="*/*/*">
+                        Playlists
+                      </translate>
                     </td>
                     <td>
                       {{ stats.playlists }}
@@ -192,7 +280,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `album:${object.id}`) }}">
-                        <translate translate-context="Content/Moderation/Table.Label/Noun">Linked reports</translate>
+                        <translate translate-context="Content/Moderation/Table.Label/Noun">
+                          Linked reports
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -202,7 +292,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.edits', query: {q: getQuery('target', 'album ' + object.id)}}">
-                        <translate translate-context="*/Admin/*/Noun">Edits</translate>
+                        <translate translate-context="*/Admin/*/Noun">
+                          Edits
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -216,25 +308,33 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="music icon"></i>
+                <i class="music icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Audio content</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Audio content
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
-
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Cached size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_downloaded_size | humanSize }}
@@ -242,7 +342,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label">Total size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label">
+                        Total size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_total_size | humanSize }}
@@ -252,7 +354,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('album_id', object.id) }}">
-                        <translate translate-context="*/*/*/Noun">Libraries</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Libraries
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -262,7 +366,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('album_id', object.id) }}">
-                        <translate translate-context="*/*/*">Uploads</translate>
+                        <translate translate-context="*/*/*">
+                          Uploads
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -272,7 +378,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('album_id', object.id) }}">
-                        <translate translate-context="*/*/*">Tracks</translate>
+                        <translate translate-context="*/*/*">
+                          Tracks
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -281,77 +389,74 @@
                   </tr>
                 </tbody>
               </table>
-
             </section>
           </div>
         </div>
       </div>
-
     </template>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import logger from "@/logging"
-import FetchButton from "@/components/federation/FetchButton"
-import TagsList from "@/components/tags/List"
+import axios from 'axios'
+import FetchButton from '@/components/federation/FetchButton'
+import TagsList from '@/components/tags/List'
 
 export default {
-  props: ["id"],
   components: {
     FetchButton,
     TagsList
   },
-  data() {
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       isLoading: true,
       isLoadingStats: false,
       object: null,
-      stats: null,
+      stats: null
+    }
+  },
+  computed: {
+    labels () {
+      return {
+        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
+      }
     }
   },
-  created() {
+  created () {
     this.fetchData()
     this.fetchStats()
   },
   methods: {
-    fetchData() {
-      var self = this
+    fetchData () {
+      const self = this
       this.isLoading = true
-      let url = `manage/library/albums/${this.id}/`
+      const url = `manage/library/albums/${this.id}/`
       axios.get(url).then(response => {
         self.object = response.data
         self.isLoading = false
       })
     },
-    fetchStats() {
-      var self = this
+    fetchStats () {
+      const self = this
       this.isLoadingStats = true
-      let url = `manage/library/albums/${this.id}/stats/`
+      const url = `manage/library/albums/${this.id}/stats/`
       axios.get(url).then(response => {
         self.stats = response.data
         self.isLoadingStats = false
       })
     },
     remove () {
-      var self = this
+      const self = this
       this.isLoading = true
-      let url = `manage/library/albums/${this.id}/`
+      const url = `manage/library/albums/${this.id}/`
       axios.delete(url).then(response => {
-        self.$router.push({name: 'manage.library.albums'})
+        self.$router.push({ name: 'manage.library.albums' })
       })
     },
     getQuery (field, value) {
       return `${field}:"${value}"`
     }
-  },
-  computed: {
-    labels() {
-      return {
-        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'),
-      }
-    },
   }
 }
 </script>
diff --git a/front/src/views/admin/library/AlbumsList.vue b/front/src/views/admin/library/AlbumsList.vue
index 650b4d693..419449959 100644
--- a/front/src/views/admin/library/AlbumsList.vue
+++ b/front/src/views/admin/library/AlbumsList.vue
@@ -1,25 +1,30 @@
 <template>
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header">{{ labels.title }}</h2>
-      <div class="ui hidden divider"></div>
-      <albums-table :update-url="true" :default-query="defaultQuery"></albums-table>
+      <h2 class="ui header">
+        {{ labels.title }}
+      </h2>
+      <div class="ui hidden divider" />
+      <albums-table
+        :update-url="true"
+        :default-query="defaultQuery"
+      />
     </section>
   </main>
 </template>
 
 <script>
-import AlbumsTable from "@/components/manage/library/AlbumsTable"
+import AlbumsTable from '@/components/manage/library/AlbumsTable'
 
 export default {
   components: {
     AlbumsTable
   },
   props: {
-    defaultQuery: {type: String, required: false},
+    defaultQuery: { type: String, required: false, default: '' }
   },
   computed: {
-    labels() {
+    labels () {
       return {
         title: this.$pgettext('*/*/*', 'Albums')
       }
diff --git a/front/src/views/admin/library/ArtistDetail.vue b/front/src/views/admin/library/ArtistDetail.vue
index e3e11c517..8da0906cc 100644
--- a/front/src/views/admin/library/ArtistDetail.vue
+++ b/front/src/views/admin/library/ArtistDetail.vue
@@ -1,22 +1,36 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object">
-      <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="object.name">
+      <section
+        v-title="object.name"
+        :class="['ui', 'head', 'vertical', 'stripe', 'segment']"
+      >
         <div class="ui stackable one column grid">
           <div class="ui column">
             <div class="segment-content">
               <h2 class="ui header">
-                <img alt="" v-if="object.cover && object.cover.urls.medium_square_crop" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.medium_square_crop)">
-                <img alt="" v-else src="../../../assets/audio/default-cover.png">
+                <img
+                  v-if="object.cover && object.cover.urls.medium_square_crop"
+                  v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.medium_square_crop)"
+                  alt=""
+                >
+                <img
+                  v-else
+                  alt=""
+                  src="../../../assets/audio/default-cover.png"
+                >
                 <div class="content">
                   {{ object.name | truncate(100) }}
                   <div class="sub header">
                     <template v-if="object.is_local">
                       <span class="ui tiny accent label">
-                        <i class="home icon"></i>
+                        <i class="home icon" />
                         <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
                       </span>
                       &nbsp;
@@ -25,38 +39,69 @@
                 </div>
               </h2>
               <template v-if="object.tags && object.tags.length > 0">
-                <tags-list :limit="5" detail-route="manage.library.tags.detail" :tags="object.tags"></tags-list>
-                <div class="ui hidden divider"></div>
+                <tags-list
+                  :limit="5"
+                  detail-route="manage.library.tags.detail"
+                  :tags="object.tags"
+                />
+                <div class="ui hidden divider" />
               </template>
 
               <div class="header-buttons">
-
                 <div class="ui icon buttons">
-                  <router-link class="ui labeled icon button" :to="{name: 'library.artists.detail', params: {id: object.id }}">
-                    <i class="info icon"></i>
-                    <translate translate-context="Content/Moderation/Link/Verb">Open local profile</translate>&nbsp;
+                  <router-link
+                    class="ui labeled icon button"
+                    :to="{name: 'library.artists.detail', params: {id: object.id }}"
+                  >
+                    <i class="info icon" />
+                    <translate translate-context="Content/Moderation/Link/Verb">
+                      Open local profile
+                    </translate>&nbsp;
                   </router-link>
-                  <button class="ui floating dropdown icon button" v-dropdown>
-                    <i class="dropdown icon"></i>
+                  <button
+                    v-dropdown
+                    class="ui floating dropdown icon button"
+                  >
+                    <i class="dropdown icon" />
                     <div class="menu">
                       <a
                         v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                         class="basic item"
                         :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/artist/${object.id}`)"
-                        target="_blank" rel="noopener noreferrer">
-                        <i class="wrench icon"></i>
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="wrench icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                       </a>
-                      <a class="basic item" v-if="object.mbid" :href="`https://musicbrainz.org/artist/${object.mbid}`" target="_blank" rel="noopener noreferrer">
-                        <i class="external icon"></i>
+                      <a
+                        v-if="object.mbid"
+                        class="basic item"
+                        :href="`https://musicbrainz.org/artist/${object.mbid}`"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="external icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">Open on MusicBrainz</translate>&nbsp;
                       </a>
-                      <fetch-button @refresh="fetchData" v-if="!object.is_local" class="basic item" :url="`artists/${object.id}/fetches/`">
-                        <i class="refresh icon"></i>&nbsp;
-                        <translate translate-context="Content/Moderation/Button/Verb">Refresh from remote server</translate>&nbsp;
+                      <fetch-button
+                        v-if="!object.is_local"
+                        class="basic item"
+                        :url="`artists/${object.id}/fetches/`"
+                        @refresh="fetchData"
+                      >
+                        <i class="refresh icon" />&nbsp;
+                        <translate translate-context="Content/Moderation/Button/Verb">
+                          Refresh from remote server
+                        </translate>&nbsp;
                       </fetch-button>
-                      <a class="basic item" :href="object.url || object.fid" target="_blank" rel="noopener noreferrer">
-                        <i class="external icon"></i>
+                      <a
+                        class="basic item"
+                        :href="object.url || object.fid"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="external icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate>&nbsp;
                       </a>
                     </div>
@@ -66,21 +111,39 @@
                   <router-link
                     v-if="object.is_local"
                     :to="{name: 'library.artists.edit', params: {id: object.id }}"
-                    class="ui labeled icon button">
-                    <i class="edit icon"></i>
-                    <translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
+                    class="ui labeled icon button"
+                  >
+                    <i class="edit icon" />
+                    <translate translate-context="Content/*/Button.Label/Verb">
+                      Edit
+                    </translate>
                   </router-link>
                 </div>
                 <div class="ui buttons">
                   <dangerous-button
                     :class="['ui', {loading: isLoading}, 'basic danger button']"
-                    :action="remove">
-                    <translate translate-context="*/*/*/Verb">Delete</translate>
-                    <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this artist?</translate></p>
+                    :action="remove"
+                  >
+                    <translate translate-context="*/*/*/Verb">
+                      Delete
+                    </translate>
+                    <p slot="modal-header">
+                      <translate translate-context="Popup/Library/Title">
+                        Delete this artist?
+                      </translate>
+                    </p>
                     <div slot="modal-content">
-                      <p><translate translate-context="Content/Moderation/Paragraph">The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible.</translate></p>
+                      <p>
+                        <translate translate-context="Content/Moderation/Paragraph">
+                          The artist will be removed, as well as associated uploads, tracks, albums, favorites and listening history. This action is irreversible.
+                        </translate>
+                      </p>
                     </div>
-                    <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p>
+                    <p slot="modal-confirm">
+                      <translate translate-context="*/*/*/Verb">
+                        Delete
+                      </translate>
+                    </p>
                   </dangerous-button>
                 </div>
               </div>
@@ -93,16 +156,20 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="info icon"></i>
+                <i class="info icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Artist data</translate>
+                  <translate translate-context="Content/Moderation/Title">
+                    Artist data
+                  </translate>
                 </div>
               </h3>
               <table class="ui very basic table">
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Name</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Name
+                      </translate>
                     </td>
                     <td>
                       {{ object.name }}
@@ -111,7 +178,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.artists', query: {q: getQuery('category', object.content_category) }}">
-                        <translate translate-context="*/*/*">Category</translate>
+                        <translate translate-context="*/*/*">
+                          Category
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -121,7 +190,9 @@
                   <tr v-if="!object.is_local">
                     <td>
                       <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}">
-                        <translate translate-context="Content/Moderation/*/Noun">Domain</translate>
+                        <translate translate-context="Content/Moderation/*/Noun">
+                          Domain
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -130,9 +201,11 @@
                   </tr>
                   <tr v-if="object.description">
                     <td>
-                      <translate translate-context="'*/*/*/Noun">Description</translate>
+                      <translate translate-context="'*/*/*/Noun">
+                        Description
+                      </translate>
                     </td>
-                    <td v-html="object.description.html"></td>
+                    <td v-html="object.description.html" />
                   </tr>
                 </tbody>
               </table>
@@ -141,32 +214,43 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="feed icon"></i>
+                <i class="feed icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Activity</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Activity
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">
+                        First seen
+                      </translate>
                     </td>
                     <td>
-                      <human-date :date="object.creation_date"></human-date>
+                      <human-date :date="object.creation_date" />
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Listenings</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Listenings
+                      </translate>
                     </td>
                     <td>
                       {{ stats.listenings }}
@@ -174,7 +258,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*">Favorited tracks</translate>
+                      <translate translate-context="*/*/*">
+                        Favorited tracks
+                      </translate>
                     </td>
                     <td>
                       {{ stats.track_favorites }}
@@ -182,7 +268,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*">Playlists</translate>
+                      <translate translate-context="*/*/*">
+                        Playlists
+                      </translate>
                     </td>
                     <td>
                       {{ stats.playlists }}
@@ -191,7 +279,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `artist:${object.id}`) }}">
-                        <translate translate-context="Content/Moderation/Table.Label/Noun">Linked reports</translate>
+                        <translate translate-context="Content/Moderation/Table.Label/Noun">
+                          Linked reports
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -201,7 +291,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.edits', query: {q: getQuery('target', 'artist ' + object.id)}}">
-                        <translate translate-context="*/Admin/*/Noun">Edits</translate>
+                        <translate translate-context="*/Admin/*/Noun">
+                          Edits
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -215,25 +307,33 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="music icon"></i>
+                <i class="music icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Audio content</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Audio content
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
-
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Cached size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_downloaded_size | humanSize }}
@@ -241,7 +341,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label">Total size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label">
+                        Total size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_total_size | humanSize }}
@@ -251,7 +353,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('artist_id', object.id) }}">
-                        <translate translate-context="*/*/*/Noun">Libraries</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Libraries
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -261,7 +365,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('artist_id', object.id) }}">
-                        <translate translate-context="*/*/*">Uploads</translate>
+                        <translate translate-context="*/*/*">
+                          Uploads
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -271,7 +377,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.albums', query: {q: getQuery('artist_id', object.id) }}">
-                        <translate translate-context="*/*/*">Albums</translate>
+                        <translate translate-context="*/*/*">
+                          Albums
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -281,7 +389,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('artist_id', object.id) }}">
-                        <translate translate-context="*/*/*">Tracks</translate>
+                        <translate translate-context="*/*/*">
+                          Tracks
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -290,82 +400,79 @@
                   </tr>
                 </tbody>
               </table>
-
             </section>
           </div>
         </div>
       </div>
-
     </template>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import logger from "@/logging"
+import axios from 'axios'
 
-import TagsList from "@/components/tags/List"
-import FetchButton from "@/components/federation/FetchButton"
+import TagsList from '@/components/tags/List'
+import FetchButton from '@/components/federation/FetchButton'
 
 export default {
-  props: ["id"],
   components: {
     FetchButton,
     TagsList
   },
-  data() {
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       isLoading: true,
       isLoadingStats: false,
       object: null,
-      stats: null,
+      stats: null
+    }
+  },
+  computed: {
+    labels () {
+      return {
+        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
+      }
     }
   },
-  created() {
+  created () {
     this.fetchData()
     this.fetchStats()
   },
   methods: {
-    fetchData() {
-      var self = this
+    fetchData () {
+      const self = this
       this.isLoading = true
-      let url = `manage/library/artists/${this.id}/`
+      const url = `manage/library/artists/${this.id}/`
       axios.get(url).then(response => {
         if (response.data.channel) {
-          self.$router.push({name: "manage.channels.detail", params: {id: response.data.channel}})
+          self.$router.push({ name: 'manage.channels.detail', params: { id: response.data.channel } })
         } else {
           self.object = response.data
           self.isLoading = false
         }
       })
     },
-    fetchStats() {
-      var self = this
+    fetchStats () {
+      const self = this
       this.isLoadingStats = true
-      let url = `manage/library/artists/${this.id}/stats/`
+      const url = `manage/library/artists/${this.id}/stats/`
       axios.get(url).then(response => {
         self.stats = response.data
         self.isLoadingStats = false
       })
     },
     remove () {
-      var self = this
+      const self = this
       this.isLoading = true
-      let url = `manage/library/artists/${this.id}/`
+      const url = `manage/library/artists/${this.id}/`
       axios.delete(url).then(response => {
-        self.$router.push({name: 'manage.library.artists'})
+        self.$router.push({ name: 'manage.library.artists' })
       })
     },
     getQuery (field, value) {
       return `${field}:"${value}"`
     }
-  },
-  computed: {
-    labels() {
-      return {
-        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'),
-      }
-    },
   }
 }
 </script>
diff --git a/front/src/views/admin/library/ArtistsList.vue b/front/src/views/admin/library/ArtistsList.vue
index dae856be4..7e07353e0 100644
--- a/front/src/views/admin/library/ArtistsList.vue
+++ b/front/src/views/admin/library/ArtistsList.vue
@@ -1,25 +1,30 @@
 <template>
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header">{{ labels.title }}</h2>
-      <div class="ui hidden divider"></div>
-      <artists-table :update-url="true" :default-query="defaultQuery"></artists-table>
+      <h2 class="ui header">
+        {{ labels.title }}
+      </h2>
+      <div class="ui hidden divider" />
+      <artists-table
+        :update-url="true"
+        :default-query="defaultQuery"
+      />
     </section>
   </main>
 </template>
 
 <script>
-import ArtistsTable from "@/components/manage/library/ArtistsTable"
+import ArtistsTable from '@/components/manage/library/ArtistsTable'
 
 export default {
   components: {
     ArtistsTable
   },
   props: {
-    defaultQuery: {type: String, required: false},
+    defaultQuery: { type: String, required: false, default: '' }
   },
   computed: {
-    labels() {
+    labels () {
       return {
         title: this.$pgettext('*/*/*/Noun', 'Artists')
       }
diff --git a/front/src/views/admin/library/Base.vue b/front/src/views/admin/library/Base.vue
index 8b2587d22..97f9013e7 100644
--- a/front/src/views/admin/library/Base.vue
+++ b/front/src/views/admin/library/Base.vue
@@ -1,41 +1,88 @@
 <template>
-  <div class="main pusher page-admin-library" v-title="labels.title">
-    <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu">
+  <div
+    v-title="labels.title"
+    class="main pusher page-admin-library"
+  >
+    <nav
+      class="ui secondary pointing menu"
+      role="navigation"
+      :aria-label="labels.secondaryMenu"
+    >
       <router-link
         class="ui item"
-        :to="{name: 'manage.library.edits'}"><translate translate-context="*/Admin/*/Noun">Edits</translate></router-link>
+        :to="{name: 'manage.library.edits'}"
+      >
+        <translate translate-context="*/Admin/*/Noun">
+          Edits
+        </translate>
+      </router-link>
       <router-link
         class="ui item"
-        :to="{name: 'manage.channels'}"><translate translate-context="*/*/*">Channels</translate></router-link>
+        :to="{name: 'manage.channels'}"
+      >
+        <translate translate-context="*/*/*">
+          Channels
+        </translate>
+      </router-link>
       <router-link
         class="ui item"
-        :to="{name: 'manage.library.artists'}"><translate translate-context="*/*/*/Noun">Artists</translate></router-link>
+        :to="{name: 'manage.library.artists'}"
+      >
+        <translate translate-context="*/*/*/Noun">
+          Artists
+        </translate>
+      </router-link>
       <router-link
         class="ui item"
-        :to="{name: 'manage.library.albums'}"><translate translate-context="*/*/*">Albums</translate></router-link>
+        :to="{name: 'manage.library.albums'}"
+      >
+        <translate translate-context="*/*/*">
+          Albums
+        </translate>
+      </router-link>
       <router-link
         class="ui item"
-        :to="{name: 'manage.library.tracks'}"><translate translate-context="*/*/*">Tracks</translate></router-link>
+        :to="{name: 'manage.library.tracks'}"
+      >
+        <translate translate-context="*/*/*">
+          Tracks
+        </translate>
+      </router-link>
       <router-link
         class="ui item"
-        :to="{name: 'manage.library.libraries'}"><translate translate-context="*/*/*/Noun">Libraries</translate></router-link>
+        :to="{name: 'manage.library.libraries'}"
+      >
+        <translate translate-context="*/*/*/Noun">
+          Libraries
+        </translate>
+      </router-link>
       <router-link
         class="ui item"
-        :to="{name: 'manage.library.uploads'}"><translate translate-context="*/*/*">Uploads</translate></router-link>
+        :to="{name: 'manage.library.uploads'}"
+      >
+        <translate translate-context="*/*/*">
+          Uploads
+        </translate>
+      </router-link>
       <router-link
         class="ui item"
-        :to="{name: 'manage.library.tags'}"><translate translate-context="*/*/*/Noun">Tags</translate></router-link>
+        :to="{name: 'manage.library.tags'}"
+      >
+        <translate translate-context="*/*/*/Noun">
+          Tags
+        </translate>
+      </router-link>
     </nav>
-    <router-view :key="$route.fullPath"></router-view>
+    <router-view :key="$route.fullPath" />
   </div>
 </template>
 
 <script>
 export default {
   computed: {
-    labels() {
-      let title = this.$pgettext('Head/Admin/Title', 'Manage library')
-      let secondaryMenu = this.$pgettext('Menu/*/Hidden text', 'Secondary menu')
+    labels () {
+      const title = this.$pgettext('Head/Admin/Title', 'Manage library')
+      const secondaryMenu = this.$pgettext('Menu/*/Hidden text', 'Secondary menu')
       return {
         title,
         secondaryMenu
diff --git a/front/src/views/admin/library/EditsList.vue b/front/src/views/admin/library/EditsList.vue
index 8af28804c..2b67c69b6 100644
--- a/front/src/views/admin/library/EditsList.vue
+++ b/front/src/views/admin/library/EditsList.vue
@@ -1,25 +1,32 @@
 <template>
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
-      <edits-card-list :update-url="true" :default-query="defaultQuery">
-        <h2 class="ui header"><translate translate-context="Content/Admin/Title/Noun">Library edits</translate></h2>
+      <edits-card-list
+        :update-url="true"
+        :default-query="defaultQuery"
+      >
+        <h2 class="ui header">
+          <translate translate-context="Content/Admin/Title/Noun">
+            Library edits
+          </translate>
+        </h2>
       </edits-card-list>
     </section>
   </main>
 </template>
 
 <script>
-import EditsCardList from "@/components/manage/library/EditsCardList"
+import EditsCardList from '@/components/manage/library/EditsCardList'
 
 export default {
-  props: {
-    defaultQuery: {type: String, required: false},
-  },
   components: {
     EditsCardList
   },
+  props: {
+    defaultQuery: { type: String, required: false, default: '' }
+  },
   computed: {
-    labels() {
+    labels () {
       return {
         title: this.$pgettext('*/Admin/*/Noun', 'Edits')
       }
diff --git a/front/src/views/admin/library/LibrariesList.vue b/front/src/views/admin/library/LibrariesList.vue
index 479008bdb..7e06cf21c 100644
--- a/front/src/views/admin/library/LibrariesList.vue
+++ b/front/src/views/admin/library/LibrariesList.vue
@@ -1,25 +1,30 @@
 <template>
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header">{{ labels.title }}</h2>
-      <div class="ui hidden divider"></div>
-      <libraries-table :update-url="true" :default-query="defaultQuery"></libraries-table>
+      <h2 class="ui header">
+        {{ labels.title }}
+      </h2>
+      <div class="ui hidden divider" />
+      <libraries-table
+        :update-url="true"
+        :default-query="defaultQuery"
+      />
     </section>
   </main>
 </template>
 
 <script>
-import LibrariesTable from "@/components/manage/library/LibrariesTable"
+import LibrariesTable from '@/components/manage/library/LibrariesTable'
 
 export default {
   components: {
     LibrariesTable
   },
   props: {
-    defaultQuery: {type: String, required: false},
+    defaultQuery: { type: String, required: false, default: '' }
   },
   computed: {
-    labels() {
+    labels () {
       return {
         title: this.$pgettext('*/*/*/Noun', 'Libraries')
       }
diff --git a/front/src/views/admin/library/LibraryDetail.vue b/front/src/views/admin/library/LibraryDetail.vue
index 6076063df..ee58f13fa 100644
--- a/front/src/views/admin/library/LibraryDetail.vue
+++ b/front/src/views/admin/library/LibraryDetail.vue
@@ -1,21 +1,27 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object">
-      <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="object.name">
+      <section
+        v-title="object.name"
+        :class="['ui', 'head', 'vertical', 'stripe', 'segment']"
+      >
         <div class="ui stackable one column grid">
           <div class="ui column">
             <div class="segment-content">
               <h2 class="ui header">
-                <i class="circular inverted book icon"></i>
+                <i class="circular inverted book icon" />
                 <div class="content">
                   {{ object.name | truncate(100) }}
                   <div class="sub header">
                     <template v-if="object.is_local">
                       <span class="ui tiny accent label">
-                        <i class="home icon"></i>
+                        <i class="home icon" />
                         <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
                       </span>
                       &nbsp;
@@ -24,29 +30,40 @@
                 </div>
               </h2>
               <div class="header-buttons">
-
                 <div class="ui icon buttons">
                   <a
                     v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                     class="ui labeled icon button"
                     :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/library/${object.id}`)"
-                    target="_blank" rel="noopener noreferrer">
-                    <i class="wrench icon"></i>
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >
+                    <i class="wrench icon" />
                     <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                   </a>
-                  <button class="ui floating dropdown icon button" v-dropdown>
-                    <i class="dropdown icon"></i>
+                  <button
+                    v-dropdown
+                    class="ui floating dropdown icon button"
+                  >
+                    <i class="dropdown icon" />
                     <div class="menu">
                       <a
                         v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                         class="basic item"
                         :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/library/${object.id}`)"
-                        target="_blank" rel="noopener noreferrer">
-                        <i class="wrench icon"></i>
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="wrench icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                       </a>
-                      <a class="basic item" :href="object.url || object.fid" target="_blank" rel="noopener noreferrer">
-                        <i class="external icon"></i>
+                      <a
+                        class="basic item"
+                        :href="object.url || object.fid"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="external icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate>&nbsp;
                       </a>
                     </div>
@@ -55,13 +72,28 @@
                 <div class="ui buttons">
                   <dangerous-button
                     :class="['ui', {loading: isLoading}, 'basic danger button']"
-                    :action="remove">
-                    <translate translate-context="*/*/*/Verb">Delete</translate>
-                    <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this library?</translate></p>
+                    :action="remove"
+                  >
+                    <translate translate-context="*/*/*/Verb">
+                      Delete
+                    </translate>
+                    <p slot="modal-header">
+                      <translate translate-context="Popup/Library/Title">
+                        Delete this library?
+                      </translate>
+                    </p>
                     <div slot="modal-content">
-                      <p><translate translate-context="Content/Moderation/Paragraph">The library will be removed, as well as associated uploads, and follows. This action is irreversible.</translate></p>
+                      <p>
+                        <translate translate-context="Content/Moderation/Paragraph">
+                          The library will be removed, as well as associated uploads, and follows. This action is irreversible.
+                        </translate>
+                      </p>
                     </div>
-                    <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p>
+                    <p slot="modal-confirm">
+                      <translate translate-context="*/*/*/Verb">
+                        Delete
+                      </translate>
+                    </p>
                   </dangerous-button>
                 </div>
               </div>
@@ -74,16 +106,20 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="info icon"></i>
+                <i class="info icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Library data</translate>
+                  <translate translate-context="Content/Moderation/Title">
+                    Library data
+                  </translate>
                 </div>
               </h3>
               <table class="ui very basic table">
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Name</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Name
+                      </translate>
                     </td>
                     <td>
                       {{ object.name }}
@@ -92,26 +128,39 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('privacy_level', object.privacy_level) }}">
-                        <translate translate-context="*/*/*">Visibility</translate>
+                        <translate translate-context="*/*/*">
+                          Visibility
+                        </translate>
                       </router-link>
                     </td>
                     <td>
                       <select
-                        v-dropdown
                         v-if="object.is_local"
-                        @change="updateObj('privacy_level')"
                         v-model="object.privacy_level"
+                        v-dropdown
+                        class="ui search selection dropdown"
 
-                        class="ui search selection dropdown">
-                        <option v-for="p in ['me', 'instance', 'everyone']" :value="p">{{ sharedLabels.fields.privacy_level.shortChoices[p] }}</option>
+                        @change="updateObj('privacy_level')"
+                      >
+                        <option
+                          v-for="(p, key) in ['me', 'instance', 'everyone']"
+                          :key="key"
+                          :value="p"
+                        >
+                          {{ sharedLabels.fields.privacy_level.shortChoices[p] }}
+                        </option>
                       </select>
-                      <template v-else>{{ sharedLabels.fields.privacy_level.shortChoices[object.privacy_level] }}</template>
+                      <template v-else>
+                        {{ sharedLabels.fields.privacy_level.shortChoices[object.privacy_level] }}
+                      </template>
                     </td>
                   </tr>
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: object.actor.full_username }}">
-                        <translate translate-context="*/*/*/Noun">Account</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Account
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -121,7 +170,9 @@
                   <tr v-if="!object.is_local">
                     <td>
                       <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}">
-                        <translate translate-context="Content/Moderation/*/Noun">Domain</translate>
+                        <translate translate-context="Content/Moderation/*/Noun">
+                          Domain
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -130,7 +181,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Description</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Description
+                      </translate>
                     </td>
                     <td>
                       {{ object.description }}
@@ -143,32 +196,43 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="feed icon"></i>
+                <i class="feed icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Activity</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Activity
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">
+                        First seen
+                      </translate>
                     </td>
                     <td>
-                      <human-date :date="object.creation_date"></human-date>
+                      <human-date :date="object.creation_date" />
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Federation/*/Noun">Followers</translate>
+                      <translate translate-context="Content/Federation/*/Noun">
+                        Followers
+                      </translate>
                     </td>
                     <td>
                       {{ stats.followers }}
@@ -177,7 +241,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `library:${object.uuid}`) }}">
-                        <translate translate-context="Content/Moderation/Table.Label/Noun">Linked reports</translate>
+                        <translate translate-context="Content/Moderation/Table.Label/Noun">
+                          Linked reports
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -191,25 +257,33 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="music icon"></i>
+                <i class="music icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Audio content</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Audio content
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
-
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Cached size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_downloaded_size | humanSize }}
@@ -217,7 +291,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label">Total size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label">
+                        Total size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_total_size | humanSize }}
@@ -226,7 +302,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.artists', query: {q: getQuery('library_id', object.id) }}">
-                        <translate translate-context="*/*/*/Noun">Artists</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Artists
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -236,7 +314,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.albums', query: {q: getQuery('library_id', object.id) }}">
-                        <translate translate-context="*/*/*">Albums</translate>
+                        <translate translate-context="*/*/*">
+                          Albums
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -246,7 +326,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('library_id', object.id) }}">
-                        <translate translate-context="*/*/*">Tracks</translate>
+                        <translate translate-context="*/*/*">
+                          Tracks
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -256,7 +338,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('library_id', object.id) }}">
-                        <translate translate-context="*/*/*">Uploads</translate>
+                        <translate translate-context="*/*/*">
+                          Uploads
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -265,75 +349,79 @@
                   </tr>
                 </tbody>
               </table>
-
             </section>
           </div>
         </div>
       </div>
-
     </template>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import logger from "@/logging"
-import TranslationsMixin from "@/components/mixins/Translations"
-
+import axios from 'axios'
+import logger from '@/logging'
+import TranslationsMixin from '@/components/mixins/Translations'
 
 export default {
-  props: ["id"],
   mixins: [
     TranslationsMixin
   ],
-  data() {
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       isLoading: true,
       isLoadingStats: false,
       object: null,
-      stats: null,
+      stats: null
     }
   },
-  created() {
+  computed: {
+    labels () {
+      return {
+        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
+      }
+    }
+  },
+  created () {
     this.fetchData()
     this.fetchStats()
   },
   methods: {
-    fetchData() {
-      var self = this
+    fetchData () {
+      const self = this
       this.isLoading = true
-      let url = `manage/library/libraries/${this.id}/`
+      const url = `manage/library/libraries/${this.id}/`
       axios.get(url).then(response => {
         self.object = response.data
         self.isLoading = false
       })
     },
-    fetchStats() {
-      var self = this
+    fetchStats () {
+      const self = this
       this.isLoadingStats = true
-      let url = `manage/library/libraries/${this.id}/stats/`
+      const url = `manage/library/libraries/${this.id}/stats/`
       axios.get(url).then(response => {
         self.stats = response.data
         self.isLoadingStats = false
       })
     },
     remove () {
-      var self = this
+      const self = this
       this.isLoading = true
-      let url = `manage/library/libraries/${this.id}/`
+      const url = `manage/library/libraries/${this.id}/`
       axios.delete(url).then(response => {
-        self.$router.push({name: 'manage.library.libraries'})
+        self.$router.push({ name: 'manage.library.libraries' })
       })
     },
     getQuery (field, value) {
       return `${field}:"${value}"`
     },
-    updateObj(attr, toNull) {
+    updateObj (attr, toNull) {
       let newValue = this.object[attr]
       if (toNull && !newValue) {
         newValue = null
       }
-      let params = {}
+      const params = {}
       params[attr] = newValue
       axios.patch(`manage/library/libraries/${this.id}/`, params).then(
         response => {
@@ -348,14 +436,7 @@ export default {
           )
         }
       )
-    },
-  },
-  computed: {
-    labels() {
-      return {
-        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'),
-      }
-    },
+    }
   }
 }
 </script>
diff --git a/front/src/views/admin/library/TagDetail.vue b/front/src/views/admin/library/TagDetail.vue
index 856478d79..b6becc046 100644
--- a/front/src/views/admin/library/TagDetail.vue
+++ b/front/src/views/admin/library/TagDetail.vue
@@ -1,35 +1,50 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object">
-      <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="object.name">
+      <section
+        v-title="object.name"
+        :class="['ui', 'head', 'vertical', 'stripe', 'segment']"
+      >
         <div class="ui stackable one column grid">
           <div class="ui column">
             <div class="segment-content">
               <h2 class="ui header">
-                <i class="circular inverted hashtag icon"></i>
+                <i class="circular inverted hashtag icon" />
                 <div class="content">
                   {{ object.name | truncate(100) }}
                 </div>
               </h2>
               <div class="header-buttons">
-
                 <div class="ui icon buttons">
-                  <router-link class="ui labeled icon button" :to="{name: 'library.tags.detail', params: {id: object.name }}">
-                    <i class="info icon"></i>
-                    <translate translate-context="Content/Moderation/Link/Verb">Open local profile</translate>&nbsp;
+                  <router-link
+                    class="ui labeled icon button"
+                    :to="{name: 'library.tags.detail', params: {id: object.name }}"
+                  >
+                    <i class="info icon" />
+                    <translate translate-context="Content/Moderation/Link/Verb">
+                      Open local profile
+                    </translate>&nbsp;
                   </router-link>
-                  <button class="ui floating dropdown icon button" v-dropdown>
-                    <i class="dropdown icon"></i>
+                  <button
+                    v-dropdown
+                    class="ui floating dropdown icon button"
+                  >
+                    <i class="dropdown icon" />
                     <div class="menu">
                       <a
                         v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                         class="basic item"
                         :href="$store.getters['instance/absoluteUrl'](`/api/admin/tags/tag/${object.id}`)"
-                        target="_blank" rel="noopener noreferrer">
-                        <i class="wrench icon"></i>
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="wrench icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                       </a>
                     </div>
@@ -38,13 +53,28 @@
                 <div class="ui buttons">
                   <dangerous-button
                     :class="['ui', {loading: isLoading}, 'basic danger button']"
-                    :action="remove">
-                    <translate translate-context="*/*/*/Verb">Delete</translate>
-                    <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this tag?</translate></p>
+                    :action="remove"
+                  >
+                    <translate translate-context="*/*/*/Verb">
+                      Delete
+                    </translate>
+                    <p slot="modal-header">
+                      <translate translate-context="Popup/Library/Title">
+                        Delete this tag?
+                      </translate>
+                    </p>
                     <div slot="modal-content">
-                      <p><translate translate-context="Content/Moderation/Paragraph">The tag will be removed and unlinked from any existing entity. This action is irreversible.</translate></p>
+                      <p>
+                        <translate translate-context="Content/Moderation/Paragraph">
+                          The tag will be removed and unlinked from any existing entity. This action is irreversible.
+                        </translate>
+                      </p>
                     </div>
-                    <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p>
+                    <p slot="modal-confirm">
+                      <translate translate-context="*/*/*/Verb">
+                        Delete
+                      </translate>
+                    </p>
                   </dangerous-button>
                 </div>
               </div>
@@ -57,16 +87,20 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="info icon"></i>
+                <i class="info icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Tag data</translate>
+                  <translate translate-context="Content/Moderation/Title">
+                    Tag data
+                  </translate>
                 </div>
               </h3>
               <table class="ui very basic table">
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Name</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Name
+                      </translate>
                     </td>
                     <td>
                       {{ object.name }}
@@ -79,27 +113,36 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="feed icon"></i>
+                <i class="feed icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Activity</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Activity
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">
+                        First seen
+                      </translate>
                     </td>
                     <td>
-                      <human-date :date="object.creation_date"></human-date>
+                      <human-date :date="object.creation_date" />
                     </td>
                   </tr>
                 </tbody>
@@ -109,11 +152,12 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="music icon"></i>
+                <i class="music icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Audio content</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Audio content
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
               <table class="ui very basic table">
@@ -121,7 +165,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.artists', query: {q: getQuery('tag', object.name) }}">
-                        <translate translate-context="*/*/*/Noun">Artists</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Artists
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -131,7 +177,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.albums', query: {q: getQuery('tag', object.name) }}">
-                        <translate translate-context="*/*/*">Albums</translate>
+                        <translate translate-context="*/*/*">
+                          Albums
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -141,7 +189,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('tag', object.name) }}">
-                        <translate translate-context="*/*/*">Tracks</translate>
+                        <translate translate-context="*/*/*">
+                          Tracks
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -150,66 +200,58 @@
                   </tr>
                 </tbody>
               </table>
-
             </section>
           </div>
         </div>
       </div>
-
     </template>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import logger from "@/logging"
-
-import FetchButton from "@/components/federation/FetchButton"
+import axios from 'axios'
 
 export default {
-  props: ["id"],
-  components: {
-    FetchButton
-  },
-  data() {
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       isLoading: true,
       isLoadingStats: false,
       object: null,
-      stats: null,
+      stats: null
     }
   },
-  created() {
+  computed: {
+    labels () {
+      return {
+        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
+      }
+    }
+  },
+  created () {
     this.fetchData()
   },
   methods: {
-    fetchData() {
-      var self = this
+    fetchData () {
+      const self = this
       this.isLoading = true
-      let url = `manage/tags/${this.id}/`
+      const url = `manage/tags/${this.id}/`
       axios.get(url).then(response => {
         self.object = response.data
         self.isLoading = false
       })
     },
     remove () {
-      var self = this
+      const self = this
       this.isLoading = true
-      let url = `manage/tags/${this.id}/`
+      const url = `manage/tags/${this.id}/`
       axios.delete(url).then(response => {
-        self.$router.push({name: 'manage.library.tags'})
+        self.$router.push({ name: 'manage.library.tags' })
       })
     },
     getQuery (field, value) {
       return `${field}:"${value}"`
     }
-  },
-  computed: {
-    labels() {
-      return {
-        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'),
-      }
-    },
   }
 }
 </script>
diff --git a/front/src/views/admin/library/TagsList.vue b/front/src/views/admin/library/TagsList.vue
index 81e892a40..8f388cdf0 100644
--- a/front/src/views/admin/library/TagsList.vue
+++ b/front/src/views/admin/library/TagsList.vue
@@ -1,25 +1,30 @@
 <template>
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header">{{ labels.title }}</h2>
-      <div class="ui hidden divider"></div>
-      <tags-table :update-url="true" :default-query="defaultQuery"></tags-table>
+      <h2 class="ui header">
+        {{ labels.title }}
+      </h2>
+      <div class="ui hidden divider" />
+      <tags-table
+        :update-url="true"
+        :default-query="defaultQuery"
+      />
     </section>
   </main>
 </template>
 
 <script>
-import TagsTable from "@/components/manage/library/TagsTable"
+import TagsTable from '@/components/manage/library/TagsTable'
 
 export default {
   components: {
     TagsTable
   },
   props: {
-    defaultQuery: {type: String, required: false},
+    defaultQuery: { type: String, required: false, default: '' }
   },
   computed: {
-    labels() {
+    labels () {
       return {
         title: this.$pgettext('*/*/*/Noun', 'Tags')
       }
diff --git a/front/src/views/admin/library/TrackDetail.vue b/front/src/views/admin/library/TrackDetail.vue
index 822942598..4a7789387 100644
--- a/front/src/views/admin/library/TrackDetail.vue
+++ b/front/src/views/admin/library/TrackDetail.vue
@@ -1,22 +1,36 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object">
-      <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="object.title">
+      <section
+        v-title="object.title"
+        :class="['ui', 'head', 'vertical', 'stripe', 'segment']"
+      >
         <div class="ui stackable one column grid">
           <div class="ui column">
             <div class="segment-content">
               <h2 class="ui header">
-                <img alt="" v-if="object.cover && object.cover.urls.medium_square_crop" v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.medium_square_crop)">
-                <img alt="" v-else src="../../../assets/audio/default-cover.png">
+                <img
+                  v-if="object.cover && object.cover.urls.medium_square_crop"
+                  v-lazy="$store.getters['instance/absoluteUrl'](object.cover.urls.medium_square_crop)"
+                  alt=""
+                >
+                <img
+                  v-else
+                  alt=""
+                  src="../../../assets/audio/default-cover.png"
+                >
                 <div class="content">
                   {{ object.title | truncate(100) }}
                   <div class="sub header">
                     <template v-if="object.is_local">
                       <span class="ui tiny accent label">
-                        <i class="home icon"></i>
+                        <i class="home icon" />
                         <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
                       </span>
                       &nbsp;
@@ -26,38 +40,69 @@
               </h2>
 
               <template v-if="object.tags && object.tags.length > 0">
-                <tags-list :limit="5" detail-route="manage.library.tags.detail" :tags="object.tags"></tags-list>
-                <div class="ui hidden divider"></div>
+                <tags-list
+                  :limit="5"
+                  detail-route="manage.library.tags.detail"
+                  :tags="object.tags"
+                />
+                <div class="ui hidden divider" />
               </template>
 
               <div class="header-buttons">
-
                 <div class="ui icon buttons">
-                  <router-link class="ui icon labeled button" :to="{name: 'library.tracks.detail', params: {id: object.id }}">
-                    <i class="info icon"></i>
-                    <translate translate-context="Content/Moderation/Link/Verb">Open local profile</translate>&nbsp;
+                  <router-link
+                    class="ui icon labeled button"
+                    :to="{name: 'library.tracks.detail', params: {id: object.id }}"
+                  >
+                    <i class="info icon" />
+                    <translate translate-context="Content/Moderation/Link/Verb">
+                      Open local profile
+                    </translate>&nbsp;
                   </router-link>
-                  <button class="ui floating dropdown icon button" v-dropdown>
-                    <i class="dropdown icon"></i>
+                  <button
+                    v-dropdown
+                    class="ui floating dropdown icon button"
+                  >
+                    <i class="dropdown icon" />
                     <div class="menu">
                       <a
                         v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                         class="basic item"
                         :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/track/${object.id}`)"
-                        target="_blank" rel="noopener noreferrer">
-                        <i class="wrench icon"></i>
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="wrench icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                       </a>
-                      <a class="basic item" v-if="object.mbid" :href="`https://musicbrainz.org/recording/${object.mbid}`" target="_blank" rel="noopener noreferrer">
-                        <i class="external icon"></i>
+                      <a
+                        v-if="object.mbid"
+                        class="basic item"
+                        :href="`https://musicbrainz.org/recording/${object.mbid}`"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="external icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">Open on MusicBrainz</translate>&nbsp;
                       </a>
-                      <fetch-button @refresh="fetchData" v-if="!object.is_local" class="basic item" :url="`tracks/${object.id}/fetches/`">
-                        <i class="refresh icon"></i>&nbsp;
-                        <translate translate-context="Content/Moderation/Button/Verb">Refresh from remote server</translate>&nbsp;
+                      <fetch-button
+                        v-if="!object.is_local"
+                        class="basic item"
+                        :url="`tracks/${object.id}/fetches/`"
+                        @refresh="fetchData"
+                      >
+                        <i class="refresh icon" />&nbsp;
+                        <translate translate-context="Content/Moderation/Button/Verb">
+                          Refresh from remote server
+                        </translate>&nbsp;
                       </fetch-button>
-                      <a class="basic item" :href="object.url || object.fid" target="_blank" rel="noopener noreferrer">
-                        <i class="external icon"></i>
+                      <a
+                        class="basic item"
+                        :href="object.url || object.fid"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="external icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate>&nbsp;
                       </a>
                     </div>
@@ -67,21 +112,39 @@
                   <router-link
                     v-if="object.is_local"
                     :to="{name: 'library.tracks.edit', params: {id: object.id }}"
-                    class="ui labeled icon button">
-                    <i class="edit icon"></i>
-                    <translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
+                    class="ui labeled icon button"
+                  >
+                    <i class="edit icon" />
+                    <translate translate-context="Content/*/Button.Label/Verb">
+                      Edit
+                    </translate>
                   </router-link>
                 </div>
                 <div class="ui buttons">
                   <dangerous-button
                     :class="['ui', {loading: isLoading}, 'basic danger button']"
-                    :action="remove">
-                    <translate translate-context="*/*/*/Verb">Delete</translate>
-                    <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this track?</translate></p>
+                    :action="remove"
+                  >
+                    <translate translate-context="*/*/*/Verb">
+                      Delete
+                    </translate>
+                    <p slot="modal-header">
+                      <translate translate-context="Popup/Library/Title">
+                        Delete this track?
+                      </translate>
+                    </p>
                     <div slot="modal-content">
-                      <p><translate translate-context="Content/Moderation/Paragraph">The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible.</translate></p>
+                      <p>
+                        <translate translate-context="Content/Moderation/Paragraph">
+                          The track will be removed, as well as associated uploads, favorites and listening history. This action is irreversible.
+                        </translate>
+                      </p>
                     </div>
-                    <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p>
+                    <p slot="modal-confirm">
+                      <translate translate-context="*/*/*/Verb">
+                        Delete
+                      </translate>
+                    </p>
                   </dangerous-button>
                 </div>
               </div>
@@ -94,16 +157,20 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="info icon"></i>
+                <i class="info icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Track data</translate>
+                  <translate translate-context="Content/Moderation/Title">
+                    Track data
+                  </translate>
                 </div>
               </h3>
               <table class="ui very basic table">
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Title</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Title
+                      </translate>
                     </td>
                     <td>
                       {{ object.title }}
@@ -112,7 +179,9 @@
                   <tr v-if="object.album">
                     <td>
                       <router-link :to="{name: 'manage.library.albums.detail', params: {id: object.album.id }}">
-                        <translate translate-context="*/*/*">Album</translate>
+                        <translate translate-context="*/*/*">
+                          Album
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -123,7 +192,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.artists.detail', params: {id: object.artist.id }}">
-                        <translate translate-context="*/*/*/Noun">Artist</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Artist
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -133,7 +204,9 @@
                   <tr v-if="object.album">
                     <td>
                       <router-link :to="{name: 'manage.library.artists.detail', params: {id: object.album.artist.id }}">
-                        <translate translate-context="*/*/*/Noun">Album artist</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Album artist
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -142,7 +215,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Short, Noun">Position</translate>
+                      <translate translate-context="*/*/*/Short, Noun">
+                        Position
+                      </translate>
                     </td>
                     <td>
                       {{ object.position }}
@@ -150,7 +225,9 @@
                   </tr>
                   <tr v-if="object.disc_number">
                     <td>
-                      <translate translate-context="*/*/*/Noun">Disc number</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Disc number
+                      </translate>
                     </td>
                     <td>
                       {{ object.disc_number }}
@@ -158,13 +235,17 @@
                   </tr>
                   <tr v-if="object.copyright">
                     <td>
-                      <translate translate-context="Content/Track/*/Noun">Copyright</translate>
+                      <translate translate-context="Content/Track/*/Noun">
+                        Copyright
+                      </translate>
                     </td>
                     <td>{{ object.copyright }}</td>
                   </tr>
                   <tr v-if="object.license">
                     <td>
-                      <translate translate-context="Content/*/*/Noun">License</translate>
+                      <translate translate-context="Content/*/*/Noun">
+                        License
+                      </translate>
                     </td>
                     <td>
                       <router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('license', object.license)}}">
@@ -175,7 +256,9 @@
                   <tr v-if="!object.is_local">
                     <td>
                       <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}">
-                        <translate translate-context="Content/Moderation/*/Noun">Domain</translate>
+                        <translate translate-context="Content/Moderation/*/Noun">
+                          Domain
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -184,9 +267,11 @@
                   </tr>
                   <tr v-if="object.description">
                     <td>
-                      <translate translate-context="'*/*/*/Noun">Description</translate>
+                      <translate translate-context="'*/*/*/Noun">
+                        Description
+                      </translate>
                     </td>
-                    <td v-html="object.description.html"></td>
+                    <td v-html="object.description.html" />
                   </tr>
                 </tbody>
               </table>
@@ -195,32 +280,43 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="feed icon"></i>
+                <i class="feed icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Activity</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Activity
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">
+                        First seen
+                      </translate>
                     </td>
                     <td>
-                      <human-date :date="object.creation_date"></human-date>
+                      <human-date :date="object.creation_date" />
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Listenings</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Listenings
+                      </translate>
                     </td>
                     <td>
                       {{ stats.listenings }}
@@ -228,7 +324,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*">Favorited tracks</translate>
+                      <translate translate-context="*/*/*">
+                        Favorited tracks
+                      </translate>
                     </td>
                     <td>
                       {{ stats.track_favorites }}
@@ -236,7 +334,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*">Playlists</translate>
+                      <translate translate-context="*/*/*">
+                        Playlists
+                      </translate>
                     </td>
                     <td>
                       {{ stats.playlists }}
@@ -245,7 +345,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `track:${object.id}`) }}">
-                        <translate translate-context="Content/Moderation/Table.Label/Noun">Linked reports</translate>
+                        <translate translate-context="Content/Moderation/Table.Label/Noun">
+                          Linked reports
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -255,7 +357,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.edits', query: {q: getQuery('target', 'track ' + object.id)}}">
-                        <translate translate-context="*/Admin/*/Noun">Edits</translate>
+                        <translate translate-context="*/Admin/*/Noun">
+                          Edits
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -269,25 +373,33 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="music icon"></i>
+                <i class="music icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Audio content</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Audio content
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
-
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Cached size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_downloaded_size | humanSize }}
@@ -295,7 +407,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label">Total size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label">
+                        Total size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_total_size | humanSize }}
@@ -305,7 +419,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('track_id', object.id) }}">
-                        <translate translate-context="*/*/*/Noun">Libraries</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Libraries
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -315,7 +431,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('track_id', object.id) }}">
-                        <translate translate-context="*/*/*">Uploads</translate>
+                        <translate translate-context="*/*/*">
+                          Uploads
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -324,78 +442,74 @@
                   </tr>
                 </tbody>
               </table>
-
             </section>
           </div>
         </div>
       </div>
-
     </template>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import logger from "@/logging"
-import FetchButton from "@/components/federation/FetchButton"
-import TagsList from "@/components/tags/List"
-
+import axios from 'axios'
+import FetchButton from '@/components/federation/FetchButton'
+import TagsList from '@/components/tags/List'
 
 export default {
-  props: ["id"],
   components: {
     FetchButton,
     TagsList
   },
-  data() {
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       isLoading: true,
       isLoadingStats: false,
       object: null,
-      stats: null,
+      stats: null
+    }
+  },
+  computed: {
+    labels () {
+      return {
+        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
+      }
     }
   },
-  created() {
+  created () {
     this.fetchData()
     this.fetchStats()
   },
   methods: {
-    fetchData() {
-      var self = this
+    fetchData () {
+      const self = this
       this.isLoading = true
-      let url = `manage/library/tracks/${this.id}/`
+      const url = `manage/library/tracks/${this.id}/`
       axios.get(url).then(response => {
         self.object = response.data
         self.isLoading = false
       })
     },
-    fetchStats() {
-      var self = this
+    fetchStats () {
+      const self = this
       this.isLoadingStats = true
-      let url = `manage/library/tracks/${this.id}/stats/`
+      const url = `manage/library/tracks/${this.id}/stats/`
       axios.get(url).then(response => {
         self.stats = response.data
         self.isLoadingStats = false
       })
     },
     remove () {
-      var self = this
+      const self = this
       this.isLoading = true
-      let url = `manage/library/tracks/${this.id}/`
+      const url = `manage/library/tracks/${this.id}/`
       axios.delete(url).then(response => {
-        self.$router.push({name: 'manage.library.tracks'})
+        self.$router.push({ name: 'manage.library.tracks' })
       })
     },
     getQuery (field, value) {
       return `${field}:"${value}"`
     }
-  },
-  computed: {
-    labels() {
-      return {
-        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'),
-      }
-    },
   }
 }
 </script>
diff --git a/front/src/views/admin/library/TracksList.vue b/front/src/views/admin/library/TracksList.vue
index 3aefc8606..4588a4e5b 100644
--- a/front/src/views/admin/library/TracksList.vue
+++ b/front/src/views/admin/library/TracksList.vue
@@ -1,25 +1,30 @@
 <template>
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header">{{ labels.title }}</h2>
-      <div class="ui hidden divider"></div>
-      <tracks-table :update-url="true" :default-query="defaultQuery"></tracks-table>
+      <h2 class="ui header">
+        {{ labels.title }}
+      </h2>
+      <div class="ui hidden divider" />
+      <tracks-table
+        :update-url="true"
+        :default-query="defaultQuery"
+      />
     </section>
   </main>
 </template>
 
 <script>
-import TracksTable from "@/components/manage/library/TracksTable"
+import TracksTable from '@/components/manage/library/TracksTable'
 
 export default {
   components: {
     TracksTable
   },
   props: {
-    defaultQuery: {type: String, required: false},
+    defaultQuery: { type: String, required: false, default: '' }
   },
   computed: {
-    labels() {
+    labels () {
       return {
         title: this.$pgettext('*/*/*', 'Tracks')
       }
diff --git a/front/src/views/admin/library/UploadDetail.vue b/front/src/views/admin/library/UploadDetail.vue
index b5a8c522b..b332c0ec3 100644
--- a/front/src/views/admin/library/UploadDetail.vue
+++ b/front/src/views/admin/library/UploadDetail.vue
@@ -1,22 +1,31 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object">
-      <import-status-modal :upload="object" :show.sync="showUploadDetailModal" />
-      <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="displayName(object)">
+      <import-status-modal
+        :upload="object"
+        :show.sync="showUploadDetailModal"
+      />
+      <section
+        v-title="displayName(object)"
+        :class="['ui', 'head', 'vertical', 'stripe', 'segment']"
+      >
         <div class="ui stackable one column grid">
           <div class="ui column">
             <div class="segment-content">
               <h2 class="ui header">
-                <i class="circular inverted file icon"></i>
+                <i class="circular inverted file icon" />
                 <div class="content">
                   {{ displayName(object) | truncate(100) }}
                   <div class="sub header">
                     <template v-if="object.is_local">
                       <span class="ui tiny accent label">
-                        <i class="home icon"></i>
+                        <i class="home icon" />
                         <translate translate-context="Content/Moderation/*/Short, Noun">Local</translate>
                       </span>
                       &nbsp;
@@ -25,50 +34,82 @@
                 </div>
               </h2>
               <div class="header-buttons">
-
                 <div class="ui icon buttons">
                   <a
                     v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                     class="ui labeled icon button"
                     :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/upload/${object.id}`)"
-                    target="_blank" rel="noopener noreferrer">
-                    <i class="wrench icon"></i>
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >
+                    <i class="wrench icon" />
                     <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                   </a>
-                  <button class="ui floating dropdown icon button" v-dropdown>
-                    <i class="dropdown icon"></i>
+                  <button
+                    v-dropdown
+                    class="ui floating dropdown icon button"
+                  >
+                    <i class="dropdown icon" />
                     <div class="menu">
                       <a
                         v-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                         class="basic item"
                         :href="$store.getters['instance/absoluteUrl'](`/api/admin/music/upload/${object.id}`)"
-                        target="_blank" rel="noopener noreferrer">
-                        <i class="wrench icon"></i>
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="wrench icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                       </a>
-                      <a class="basic item" :href="object.url || object.fid" target="_blank" rel="noopener noreferrer">
-                        <i class="external icon"></i>
+                      <a
+                        class="basic item"
+                        :href="object.url || object.fid"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="external icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate>&nbsp;
                       </a>
                     </div>
                   </button>
                 </div>
                 <div class="ui buttons">
-                  <a class="ui labeled icon button" v-if="object.audio_file" :href="$store.getters['instance/absoluteUrl'](object.audio_file)" target="_blank" rel="noopener noreferrer">
-                    <i class="download icon"></i>
+                  <a
+                    v-if="object.audio_file"
+                    class="ui labeled icon button"
+                    :href="$store.getters['instance/absoluteUrl'](object.audio_file)"
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >
+                    <i class="download icon" />
                     <translate translate-context="Content/Track/Link/Verb">Download</translate>
                   </a>
                 </div>
                 <div class="ui buttons">
                   <dangerous-button
                     :class="['ui', {loading: isLoading}, 'basic danger button']"
-                    :action="remove">
-                    <translate translate-context="*/*/*/Verb">Delete</translate>
-                    <p slot="modal-header"><translate translate-context="Popup/Library/Title">Delete this upload?</translate></p>
+                    :action="remove"
+                  >
+                    <translate translate-context="*/*/*/Verb">
+                      Delete
+                    </translate>
+                    <p slot="modal-header">
+                      <translate translate-context="Popup/Library/Title">
+                        Delete this upload?
+                      </translate>
+                    </p>
                     <div slot="modal-content">
-                      <p><translate translate-context="Content/Moderation/Paragraph">The upload will be removed. This action is irreversible.</translate></p>
+                      <p>
+                        <translate translate-context="Content/Moderation/Paragraph">
+                          The upload will be removed. This action is irreversible.
+                        </translate>
+                      </p>
                     </div>
-                    <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p>
+                    <p slot="modal-confirm">
+                      <translate translate-context="*/*/*/Verb">
+                        Delete
+                      </translate>
+                    </p>
                   </dangerous-button>
                 </div>
               </div>
@@ -81,16 +122,20 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="info icon"></i>
+                <i class="info icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Upload data</translate>
+                  <translate translate-context="Content/Moderation/Title">
+                    Upload data
+                  </translate>
                 </div>
               </h3>
               <table class="ui very basic table">
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Name</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Name
+                      </translate>
                     </td>
                     <td>
                       {{ displayName(object) }}
@@ -99,7 +144,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('privacy_level', object.library.privacy_level) }}">
-                        <translate translate-context="*/*/*">Visibility</translate>
+                        <translate translate-context="*/*/*">
+                          Visibility
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -109,7 +156,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.moderation.accounts.detail', params: {id: object.library.actor.full_username }}">
-                        <translate translate-context="*/*/*/Noun">Account</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Account
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -119,7 +168,9 @@
                   <tr v-if="!object.is_local">
                     <td>
                       <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}">
-                        <translate translate-context="Content/Moderation/*/Noun">Domain</translate>
+                        <translate translate-context="Content/Moderation/*/Noun">
+                          Domain
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -129,20 +180,28 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('status', object.import_status) }}">
-                        <translate translate-context="Content/*/*/Noun">Import status</translate>
+                        <translate translate-context="Content/*/*/Noun">
+                          Import status
+                        </translate>
                       </router-link>
                     </td>
                     <td>
                       {{ sharedLabels.fields.import_status.choices[object.import_status].label }}
-                      <button class="ui tiny basic icon button" :title="sharedLabels.fields.import_status.detailTitle" @click="detailedUpload = object; showUploadDetailModal = true">
-                        <i class="question circle outline icon"></i>
+                      <button
+                        class="ui tiny basic icon button"
+                        :title="sharedLabels.fields.import_status.detailTitle"
+                        @click="detailedUpload = object; showUploadDetailModal = true"
+                      >
+                        <i class="question circle outline icon" />
                       </button>
                     </td>
                   </tr>
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.libraries.detail', params: {id: object.library.uuid }}">
-                        <translate translate-context="*/*/*/Noun">Library</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Library
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -156,28 +215,42 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="feed icon"></i>
+                <i class="feed icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Activity</translate>&nbsp;
+                  <translate translate-context="Content/Moderation/Title">
+                    Activity
+                  </translate>&nbsp;
                 </div>
               </h3>
               <table class="ui very basic table">
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">
+                        First seen
+                      </translate>
                     </td>
                     <td>
-                      <human-date :date="object.creation_date"></human-date>
+                      <human-date :date="object.creation_date" />
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/*/*/Noun">Accessed date</translate>
+                      <translate translate-context="Content/*/*/Noun">
+                        Accessed date
+                      </translate>
                     </td>
                     <td>
-                      <human-date v-if="object.accessed_date" :date="object.accessed_date"></human-date>
-                      <translate v-else translate-context="*/*/*">N/A</translate>
+                      <human-date
+                        v-if="object.accessed_date"
+                        :date="object.accessed_date"
+                      />
+                      <translate
+                        v-else
+                        translate-context="*/*/*"
+                      >
+                        N/A
+                      </translate>
                     </td>
                   </tr>
                 </tbody>
@@ -187,9 +260,11 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="music icon"></i>
+                <i class="music icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Audio content</translate>&nbsp;
+                  <translate translate-context="Content/Moderation/Title">
+                    Audio content
+                  </translate>&nbsp;
                 </div>
               </h3>
               <table class="ui very basic table">
@@ -197,7 +272,9 @@
                   <tr v-if="object.track">
                     <td>
                       <router-link :to="{name: 'manage.library.tracks.detail', params: {id: object.track.id }}">
-                        <translate translate-context="*/*/*/Noun">Track</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Track
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -206,18 +283,27 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Cached size
+                      </translate>
                     </td>
                     <td>
                       <template v-if="object.audio_file">
                         {{ object.size | humanSize }}
                       </template>
-                      <translate v-else translate-context="*/*/*">N/A</translate>
+                      <translate
+                        v-else
+                        translate-context="*/*/*"
+                      >
+                        N/A
+                      </translate>
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/*/*/Noun">Size</translate>
+                      <translate translate-context="Content/*/*/Noun">
+                        Size
+                      </translate>
                     </td>
                     <td>
                       {{ object.size | humanSize }}
@@ -225,37 +311,58 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Track/*/Noun">Bitrate</translate>
+                      <translate translate-context="Content/Track/*/Noun">
+                        Bitrate
+                      </translate>
                     </td>
                     <td>
                       <template v-if="object.bitrate">
                         {{ object.bitrate | humanSize }}/s
                       </template>
-                      <translate v-else translate-context="*/*/*">N/A</translate>
+                      <translate
+                        v-else
+                        translate-context="*/*/*"
+                      >
+                        N/A
+                      </translate>
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/*/*">Duration</translate>
+                      <translate translate-context="Content/*/*">
+                        Duration
+                      </translate>
                     </td>
                     <td>
                       <template v-if="object.duration">
                         {{ object.duration | duration }}
                       </template>
-                      <translate v-else translate-context="*/*/*">N/A</translate>
+                      <translate
+                        v-else
+                        translate-context="*/*/*"
+                      >
+                        N/A
+                      </translate>
                     </td>
                   </tr>
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('type', object.mimetype) }}">
-                        <translate translate-context="Content/Track/Table.Label/Noun">Type</translate>
+                        <translate translate-context="Content/Track/Table.Label/Noun">
+                          Type
+                        </translate>
                       </router-link>
                     </td>
                     <td>
                       <template v-if="object.mimetype">
                         {{ object.mimetype }}
                       </template>
-                      <translate v-else translate-context="*/*/*">N/A</translate>
+                      <translate
+                        v-else
+                        translate-context="*/*/*"
+                      >
+                        N/A
+                      </translate>
                     </td>
                   </tr>
                 </tbody>
@@ -264,56 +371,60 @@
           </div>
         </div>
       </div>
-
     </template>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import logger from "@/logging"
-import TranslationsMixin from "@/components/mixins/Translations"
+import axios from 'axios'
+import TranslationsMixin from '@/components/mixins/Translations'
 import ImportStatusModal from '@/components/library/ImportStatusModal'
 import time from '@/utils/time'
 
-
 export default {
-  props: ["id"],
-  mixins: [
-    TranslationsMixin,
-  ],
   components: {
     ImportStatusModal
   },
-  data() {
+  mixins: [
+    TranslationsMixin
+  ],
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       time,
       detailedUpload: null,
       showUploadDetailModal: false,
       isLoading: true,
       object: null,
-      stats: null,
+      stats: null
     }
   },
-  created() {
+  computed: {
+    labels () {
+      return {
+        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object')
+      }
+    }
+  },
+  created () {
     this.fetchData()
   },
   methods: {
-    fetchData() {
-      var self = this
+    fetchData () {
+      const self = this
       this.isLoading = true
-      let url = `manage/library/uploads/${this.id}/`
+      const url = `manage/library/uploads/${this.id}/`
       axios.get(url).then(response => {
         self.object = response.data
         self.isLoading = false
       })
     },
     remove () {
-      var self = this
+      const self = this
       this.isLoading = true
-      let url = `manage/library/uploads/${this.id}/`
+      const url = `manage/library/uploads/${this.id}/`
       axios.delete(url).then(response => {
-        self.$router.push({name: 'manage.library.uploads'})
+        self.$router.push({ name: 'manage.library.uploads' })
       })
     },
     getQuery (field, value) {
@@ -328,13 +439,6 @@ export default {
       }
       return upload.uuid
     }
-  },
-  computed: {
-    labels() {
-      return {
-        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this object'),
-      }
-    },
   }
 }
 </script>
diff --git a/front/src/views/admin/library/UploadsList.vue b/front/src/views/admin/library/UploadsList.vue
index 0d4d7b5e3..2e2a5d778 100644
--- a/front/src/views/admin/library/UploadsList.vue
+++ b/front/src/views/admin/library/UploadsList.vue
@@ -1,25 +1,30 @@
 <template>
   <main v-title="labels.title">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header">{{ labels.title }}</h2>
-      <div class="ui hidden divider"></div>
-      <uploads-table :update-url="true" :default-query="defaultQuery"></uploads-table>
+      <h2 class="ui header">
+        {{ labels.title }}
+      </h2>
+      <div class="ui hidden divider" />
+      <uploads-table
+        :update-url="true"
+        :default-query="defaultQuery"
+      />
     </section>
   </main>
 </template>
 
 <script>
-import UploadsTable from "@/components/manage/library/UploadsTable"
+import UploadsTable from '@/components/manage/library/UploadsTable'
 
 export default {
   components: {
     UploadsTable
   },
   props: {
-    defaultQuery: {type: String, required: false},
+    defaultQuery: { type: String, required: false, default: '' }
   },
   computed: {
-    labels() {
+    labels () {
       return {
         title: this.$pgettext('*/*/*', 'Uploads')
       }
diff --git a/front/src/views/admin/moderation/AccountsDetail.vue b/front/src/views/admin/moderation/AccountsDetail.vue
index 0a78f345a..03d6d8ec0 100644
--- a/front/src/views/admin/moderation/AccountsDetail.vue
+++ b/front/src/views/admin/moderation/AccountsDetail.vue
@@ -1,28 +1,38 @@
 <template>
   <main class="page-admin-account-detail">
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object">
-      <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="object.full_username">
+      <section
+        v-title="object.full_username"
+        :class="['ui', 'head', 'vertical', 'stripe', 'segment']"
+      >
         <div class="ui stackable two column grid">
           <div class="ui column">
             <div class="segment-content">
               <h2 class="ui header">
-                <i class="circular inverted user icon"></i>
+                <i class="circular inverted user icon" />
                 <div class="content">
                   {{ object.full_username }}
                   <div class="sub header">
                     <template v-if="object.user">
                       <span class="ui tiny accent label">
-                        <i class="home icon"></i>
+                        <i class="home icon" />
                         <translate translate-context="Content/Moderation/*/Short, Noun">Local account</translate>
                       </span>
                       &nbsp;
                     </template>
-                    <a :href="object.url || object.fid" target="_blank" rel="noopener noreferrer">
+                    <a
+                      :href="object.url || object.fid"
+                      target="_blank"
+                      rel="noopener noreferrer"
+                    >
                       <translate translate-context="Content/Moderation/Link/Verb">Open profile</translate>&nbsp;
-                      <i class="external icon"></i>
+                      <i class="external icon" />
                     </a>
                   </div>
                 </div>
@@ -33,23 +43,35 @@
                     v-if="object.user && $store.state.auth.profile && $store.state.auth.profile.is_superuser"
                     class="ui labeled icon button"
                     :href="$store.getters['instance/absoluteUrl'](`/api/admin/users/user/${object.user.id}`)"
-                    target="_blank" rel="noopener noreferrer">
-                    <i class="wrench icon"></i>
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >
+                    <i class="wrench icon" />
                     <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                   </a>
                   <a
                     v-else-if="$store.state.auth.profile && $store.state.auth.profile.is_superuser"
                     class="ui labeled icon button"
                     :href="$store.getters['instance/absoluteUrl'](`/api/admin/federation/actor/${object.id}`)"
-                    target="_blank" rel="noopener noreferrer">
-                    <i class="wrench icon"></i>
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >
+                    <i class="wrench icon" />
                     <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                   </a>
-                  <button class="ui floating dropdown icon button" v-dropdown>
-                    <i class="dropdown icon"></i>
+                  <button
+                    v-dropdown
+                    class="ui floating dropdown icon button"
+                  >
+                    <i class="dropdown icon" />
                     <div class="menu">
-                      <a class="basic item" :href="object.url || object.fid" target="_blank" rel="noopener noreferrer">
-                        <i class="external icon"></i>
+                      <a
+                        class="basic item"
+                        :href="object.url || object.fid"
+                        target="_blank"
+                        rel="noopener noreferrer"
+                      >
+                        <i class="external icon" />
                         <translate translate-context="Content/Moderation/Link/Verb">Open remote profile</translate>&nbsp;
                       </a>
                     </div>
@@ -59,41 +81,64 @@
             </div>
           </div>
           <div class="ui column">
-            <div v-if="!object.user" class="ui compact clearing placeholder segment component-placeholder">
+            <div
+              v-if="!object.user"
+              class="ui compact clearing placeholder segment component-placeholder"
+            >
               <template v-if="isLoadingPolicy">
                 <div class="paragraph">
-                  <div class="line"></div>
-                  <div class="line"></div>
-                  <div class="line"></div>
-                  <div class="line"></div>
-                  <div class="line"></div>
+                  <div class="line" />
+                  <div class="line" />
+                  <div class="line" />
+                  <div class="line" />
+                  <div class="line" />
                 </div>
               </template>
               <template v-else-if="!policy && !showPolicyForm">
                 <header class="ui header">
                   <h3>
-                    <i class="shield icon"></i>
-                    <translate translate-context="Content/Moderation/Card.Title">You don't have any rule in place for this account.</translate>
+                    <i class="shield icon" />
+                    <translate translate-context="Content/Moderation/Card.Title">
+                      You don't have any rule in place for this account.
+                    </translate>
                   </h3>
                 </header>
-                <p><translate translate-context="Content/Moderation/Card.Paragraph">Moderation policies help you control how your instance interact with a given domain or account.</translate></p>
-                <button @click="showPolicyForm = true" class="ui primary button"><translate translate-context="Content/Moderation/Button/Verb">Add a moderation policy</translate></button>
+                <p>
+                  <translate translate-context="Content/Moderation/Card.Paragraph">
+                    Moderation policies help you control how your instance interact with a given domain or account.
+                  </translate>
+                </p>
+                <button
+                  class="ui primary button"
+                  @click="showPolicyForm = true"
+                >
+                  <translate translate-context="Content/Moderation/Button/Verb">
+                    Add a moderation policy
+                  </translate>
+                </button>
               </template>
-              <instance-policy-card v-else-if="policy && !showPolicyForm" :object="policy" @update="showPolicyForm = true">
+              <instance-policy-card
+                v-else-if="policy && !showPolicyForm"
+                :object="policy"
+                @update="showPolicyForm = true"
+              >
                 <header class="ui header">
                   <h3>
-                    <translate translate-context="Content/Moderation/Card.Title">This domain is subject to specific moderation rules</translate>
+                    <translate translate-context="Content/Moderation/Card.Title">
+                      This domain is subject to specific moderation rules
+                    </translate>
                   </h3>
                 </header>
               </instance-policy-card>
               <instance-policy-form
                 v-else-if="showPolicyForm"
+                :object="policy"
+                type="actor"
+                :target="object.full_username"
                 @cancel="showPolicyForm = false"
                 @save="updatePolicy"
                 @delete="policy = null; showPolicyForm = false"
-                :object="policy"
-                type="actor"
-                :target="object.full_username" />
+              />
             </div>
           </div>
         </div>
@@ -103,16 +148,20 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="info icon"></i>
+                <i class="info icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Account data</translate>
+                  <translate translate-context="Content/Moderation/Title">
+                    Account data
+                  </translate>
                 </div>
               </h3>
               <table class="ui very basic table">
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="Content/*/*">Username</translate>
+                      <translate translate-context="Content/*/*">
+                        Username
+                      </translate>
                     </td>
                     <td>
                       {{ object.preferred_username }}
@@ -121,7 +170,9 @@
                   <tr v-if="!object.user">
                     <td>
                       <router-link :to="{name: 'manage.moderation.domains.detail', params: {id: object.domain }}">
-                        <translate translate-context="Content/Moderation/*/Noun">Domain</translate>
+                        <translate translate-context="Content/Moderation/*/Noun">
+                          Domain
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -130,7 +181,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="'Content/*/*/Noun'">Display name</translate>
+                      <translate translate-context="'Content/*/*/Noun'">
+                        Display name
+                      </translate>
                     </td>
                     <td>
                       {{ object.name }}
@@ -138,7 +191,9 @@
                   </tr>
                   <tr v-if="object.user">
                     <td>
-                      <translate translate-context="Content/*/*">Email address</translate>
+                      <translate translate-context="Content/*/*">
+                        Email address
+                      </translate>
                     </td>
                     <td>
                       {{ object.user.email }}
@@ -146,41 +201,79 @@
                   </tr>
                   <tr v-if="object.user">
                     <td>
-                      <translate translate-context="Content/*/*/Noun">Login status</translate>
+                      <translate translate-context="Content/*/*/Noun">
+                        Login status
+                      </translate>
                     </td>
                     <td>
-                      <div class="ui toggle checkbox" v-if="object.user.username != $store.state.auth.profile.username">
+                      <div
+                        v-if="object.user.username != $store.state.auth.profile.username"
+                        class="ui toggle checkbox"
+                      >
                         <input
                           id="is-active"
+                          v-model="object.user.is_active"
+                          type="checkbox"
                           @change="updateUser('is_active')"
-                          v-model="object.user.is_active" type="checkbox">
+                        >
                         <label for="is-active">
-                          <translate v-if="object.user.is_active" key="1" translate-context="*/*/*/State of feature">Enabled</translate>
-                          <translate v-else key="2" translate-context="*/*/*/State of feature">Disabled</translate>
+                          <translate
+                            v-if="object.user.is_active"
+                            key="1"
+                            translate-context="*/*/*/State of feature"
+                          >Enabled</translate>
+                          <translate
+                            v-else
+                            key="2"
+                            translate-context="*/*/*/State of feature"
+                          >Disabled</translate>
                         </label>
                       </div>
-                      <translate v-else-if="object.user.is_active" key="1" translate-context="*/*/*/State of feature">Enabled</translate>
-                      <translate v-else key="2" translate-context="*/*/*/State of feature">Disabled</translate>
+                      <translate
+                        v-else-if="object.user.is_active"
+                        key="1"
+                        translate-context="*/*/*/State of feature"
+                      >
+                        Enabled
+                      </translate>
+                      <translate
+                        v-else
+                        key="2"
+                        translate-context="*/*/*/State of feature"
+                      >
+                        Disabled
+                      </translate>
                     </td>
                   </tr>
                   <tr v-if="object.user">
                     <td>
-                      <translate translate-context="Content/*/*/Noun">Permissions</translate>
+                      <translate translate-context="Content/*/*/Noun">
+                        Permissions
+                      </translate>
                     </td>
                     <td>
                       <select
-                        @change="updateUser('permissions')"
                         v-model="permissions"
                         multiple
-                        class="ui search selection dropdown">
-                        <option v-for="p in allPermissions" :value="p.code">{{ p.label }}</option>
+                        class="ui search selection dropdown"
+                        @change="updateUser('permissions')"
+                      >
+                        <option
+                          v-for="(p, key) in allPermissions"
+                          :key="key"
+                          :value="p.code"
+                        >
+                          {{ p.label }}
+                        </option>
                       </select>
-                      <action-feedback :is-loading="updating.permissions"></action-feedback>
+                      <action-feedback :is-loading="updating.permissions" />
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Track/Table.Label/Noun">Type</translate>
+                      <translate translate-context="Content/Track/Table.Label/Noun">
+                        Type
+                      </translate>
                     </td>
                     <td>
                       {{ object.type }}
@@ -188,27 +281,41 @@
                   </tr>
                   <tr v-if="!object.user">
                     <td>
-                      <translate translate-context="Content/*/Table.Label">Last checked</translate>
+                      <translate translate-context="Content/*/Table.Label">
+                        Last checked
+                      </translate>
                     </td>
                     <td>
-                      <human-date v-if="object.last_fetch_date" :date="object.last_fetch_date"></human-date>
-                      <translate v-else translate-context="*/*/*">N/A</translate>
+                      <human-date
+                        v-if="object.last_fetch_date"
+                        :date="object.last_fetch_date"
+                      />
+                      <translate
+                        v-else
+                        translate-context="*/*/*"
+                      >
+                        N/A
+                      </translate>
                     </td>
                   </tr>
                   <tr v-if="object.user">
                     <td>
-                      <translate translate-context="Content/Admin/Table.Label/Noun">Sign-up date</translate>
+                      <translate translate-context="Content/Admin/Table.Label/Noun">
+                        Sign-up date
+                      </translate>
                     </td>
                     <td>
-                      <human-date :date="object.user.date_joined"></human-date>
+                      <human-date :date="object.user.date_joined" />
                     </td>
                   </tr>
                   <tr v-if="object.user">
                     <td>
-                      <translate translate-context="Content/Profile/Table.Label/Short, Noun (Value is a date)">Last activity</translate>
+                      <translate translate-context="Content/Profile/Table.Label/Short, Noun (Value is a date)">
+                        Last activity
+                      </translate>
                     </td>
                     <td>
-                      <human-date :date="object.user.last_activity"></human-date>
+                      <human-date :date="object.user.last_activity" />
                     </td>
                   </tr>
                 </tbody>
@@ -218,57 +325,74 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="feed icon"></i>
+                <i class="feed icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Activity</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Activity
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
                   <tr v-if="!object.user">
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">
+                        First seen
+                      </translate>
                     </td>
                     <td>
-                      <human-date :date="object.creation_date"></human-date>
+                      <human-date :date="object.creation_date" />
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Emitted messages</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Emitted messages
+                      </translate>
                     </td>
                     <td>
-                      {{ stats.outbox_activities}}
+                      {{ stats.outbox_activities }}
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Received library follows</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Received library follows
+                      </translate>
                     </td>
                     <td>
-                      {{ stats.received_library_follows}}
+                      {{ stats.received_library_follows }}
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Emitted library follows</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Emitted library follows
+                      </translate>
                     </td>
                     <td>
-                      {{ stats.emitted_library_follows}}
+                      {{ stats.emitted_library_follows }}
                     </td>
                   </tr>
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.moderation.reports.list', query: {q: getQuery('target', `account:${object.full_username}`) }}">
-                        <translate translate-context="Content/Moderation/Table.Label/Noun">Linked reports</translate>
+                        <translate translate-context="Content/Moderation/Table.Label/Noun">
+                          Linked reports
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -278,7 +402,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.moderation.requests.list', query: {q: getQuery('submitter', `${object.full_username}`) }}">
-                        <translate translate-context="Content/Moderation/Table.Label/Noun">Requests</translate>
+                        <translate translate-context="Content/Moderation/Table.Label/Noun">
+                          Requests
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -292,25 +418,33 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="music icon"></i>
+                <i class="music icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Audio content</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Audio content
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
-
                   <tr v-if="!object.user">
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Cached size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_downloaded_size | humanSize }}
@@ -318,27 +452,38 @@
                   </tr>
                   <tr v-if="object.user">
                     <td>
-                      <translate translate-context="*/*/*" >Upload quota</translate>
-                      <span :data-tooltip="labels.uploadQuota"><i class="question circle icon"></i></span>
+                      <translate translate-context="*/*/*">
+                        Upload quota
+                      </translate>
+                      <span :data-tooltip="labels.uploadQuota"><i class="question circle icon" /></span>
                     </td>
                     <td>
                       <div class="ui right labeled input">
                         <input
-                          @change="updateUser('upload_quota', true)"
                           v-model.number="object.user.upload_quota"
                           step="100"
                           name="quota"
-                          type="number" />
+                          type="number"
+                          @change="updateUser('upload_quota', true)"
+                        >
                         <div class="ui basic label">
-                          <translate translate-context="Content/*/*/Unit">MB</translate>&#32;
+                          <translate translate-context="Content/*/*/Unit">
+                            MB
+                          </translate>&#32;
                         </div>
-                        <action-feedback class="ui basic label" size="tiny" :is-loading="updating.upload_quota"></action-feedback>
+                        <action-feedback
+                          class="ui basic label"
+                          size="tiny"
+                          :is-loading="updating.upload_quota"
+                        />
                       </div>
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label">Total size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label">
+                        Total size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_total_size | humanSize }}
@@ -347,7 +492,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.channels', query: {q: getQuery('account', object.full_username) }}">
-                        <translate translate-context="*/*/*">Channels</translate>
+                        <translate translate-context="*/*/*">
+                          Channels
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -357,7 +504,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('account', object.full_username) }}">
-                        <translate translate-context="*/*/*/Noun">Libraries</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Libraries
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -367,7 +516,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('account', object.full_username) }}">
-                        <translate translate-context="*/*/*">Uploads</translate>
+                        <translate translate-context="*/*/*">
+                          Uploads
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -376,7 +527,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*/Noun">Artists</translate>
+                      <translate translate-context="*/*/*/Noun">
+                        Artists
+                      </translate>
                     </td>
                     <td>
                       {{ stats.artists }}
@@ -384,15 +537,19 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*">Albums</translate>
+                      <translate translate-context="*/*/*">
+                        Albums
+                      </translate>
                     </td>
                     <td>
-                      {{ stats.albums}}
+                      {{ stats.albums }}
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="*/*/*">Tracks</translate>
+                      <translate translate-context="*/*/*">
+                        Tracks
+                      </translate>
                     </td>
                     <td>
                       {{ stats.tracks }}
@@ -400,32 +557,30 @@
                   </tr>
                 </tbody>
               </table>
-
             </section>
           </div>
         </div>
       </div>
-
     </template>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import logger from "@/logging"
+import axios from 'axios'
+import logger from '@/logging'
 import lodash from '@/lodash'
-import $ from "jquery"
+import $ from 'jquery'
 
-import InstancePolicyForm from "@/components/manage/moderation/InstancePolicyForm"
-import InstancePolicyCard from "@/components/manage/moderation/InstancePolicyCard"
+import InstancePolicyForm from '@/components/manage/moderation/InstancePolicyForm'
+import InstancePolicyCard from '@/components/manage/moderation/InstancePolicyCard'
 
 export default {
-  props: ["id"],
   components: {
     InstancePolicyForm,
-    InstancePolicyCard,
+    InstancePolicyCard
   },
-  data() {
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       lodash,
       isLoading: true,
@@ -437,19 +592,50 @@ export default {
       permissions: [],
       updating: {
         permissions: false,
-        upload_quota: false,
+        upload_quota: false
       }
     }
   },
-  created() {
+  computed: {
+    labels () {
+      return {
+        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account'),
+        uploadQuota: this.$pgettext('Content/Moderation/Help text', 'Determine how much content the user can upload. Leave empty to use the default value of the instance.')
+      }
+    },
+    allPermissions () {
+      return [
+        {
+          code: 'library',
+          label: this.$pgettext('*/*/*/Noun', 'Library')
+        },
+        {
+          code: 'moderation',
+          label: this.$pgettext('*/Moderation/*', 'Moderation')
+        },
+        {
+          code: 'settings',
+          label: this.$pgettext('*/*/*/Noun', 'Settings')
+        }
+      ]
+    }
+  },
+  watch: {
+    object () {
+      this.$nextTick(() => {
+        $(this.$el).find('select.dropdown').dropdown()
+      })
+    }
+  },
+  created () {
     this.fetchData()
     this.fetchStats()
   },
   methods: {
-    fetchData() {
-      var self = this
+    fetchData () {
+      const self = this
       this.isLoading = true
-      let url = "manage/accounts/" + this.id + "/"
+      const url = 'manage/accounts/' + this.id + '/'
       axios.get(url).then(response => {
         self.object = response.data
         self.isLoading = false
@@ -465,19 +651,19 @@ export default {
         }
       })
     },
-    fetchPolicy(id) {
-      var self = this
+    fetchPolicy (id) {
+      const self = this
       this.isLoadingPolicy = true
-      let url = `manage/moderation/instance-policies/${id}/`
+      const url = `manage/moderation/instance-policies/${id}/`
       axios.get(url).then(response => {
         self.policy = response.data
         self.isLoadingPolicy = false
       })
     },
-    fetchStats() {
-      var self = this
+    fetchStats () {
+      const self = this
       this.isLoadingStats = true
-      let url = "manage/accounts/" + this.id + "/stats/"
+      const url = 'manage/accounts/' + this.id + '/stats/'
       axios.get(url).then(response => {
         self.stats = response.data
         self.isLoadingStats = false
@@ -488,18 +674,18 @@ export default {
       this.object.nodeinfo_fetch_date = new Date()
     },
 
-    updateUser(attr, toNull) {
+    updateUser (attr, toNull) {
       let newValue = this.object.user[attr]
       if (toNull && !newValue) {
         newValue = null
       }
-      let self = this
+      const self = this
       this.updating[attr] = true
-      let params = {}
-      if (attr === "permissions") {
-        params["permissions"] = {}
+      const params = {}
+      if (attr === 'permissions') {
+        params.permissions = {}
         this.allPermissions.forEach(p => {
-          params["permissions"][p.code] = this.permissions.indexOf(p.code) > -1
+          params.permissions[p.code] = this.permissions.indexOf(p.code) > -1
         })
       } else {
         params[attr] = newValue
@@ -523,37 +709,6 @@ export default {
     getQuery (field, value) {
       return `${field}:"${value}"`
     }
-  },
-  computed: {
-    labels() {
-      return {
-        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this account'),
-        uploadQuota: this.$pgettext('Content/Moderation/Help text', 'Determine how much content the user can upload. Leave empty to use the default value of the instance.'),
-      }
-    },
-    allPermissions() {
-      return [
-        {
-          code: "library",
-          label: this.$pgettext('*/*/*/Noun', "Library")
-        },
-        {
-          code: "moderation",
-          label: this.$pgettext('*/Moderation/*', "Moderation")
-        },
-        {
-          code: "settings",
-          label: this.$pgettext('*/*/*/Noun', "Settings")
-        }
-      ]
-    }
-  },
-  watch: {
-    object () {
-      this.$nextTick(() => {
-        $(this.$el).find("select.dropdown").dropdown()
-      })
-    }
   }
 }
 </script>
diff --git a/front/src/views/admin/moderation/AccountsList.vue b/front/src/views/admin/moderation/AccountsList.vue
index 1fbc57ae6..559694464 100644
--- a/front/src/views/admin/moderation/AccountsList.vue
+++ b/front/src/views/admin/moderation/AccountsList.vue
@@ -1,27 +1,34 @@
 <template>
   <main v-title="labels.accounts">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header"><translate translate-context="*/Moderation/Title">Accounts</translate></h2>
-      <div class="ui hidden divider"></div>
-      <accounts-table :update-url="true" :default-query="defaultQuery"></accounts-table>
+      <h2 class="ui header">
+        <translate translate-context="*/Moderation/Title">
+          Accounts
+        </translate>
+      </h2>
+      <div class="ui hidden divider" />
+      <accounts-table
+        :update-url="true"
+        :default-query="defaultQuery"
+      />
     </section>
   </main>
 </template>
 
 <script>
-import AccountsTable from "@/components/manage/moderation/AccountsTable"
+import AccountsTable from '@/components/manage/moderation/AccountsTable'
 
 export default {
   components: {
     AccountsTable
   },
   props: {
-    defaultQuery: {type: String, required: false},
+    defaultQuery: { type: String, required: false, default: '' }
   },
   computed: {
-    labels() {
+    labels () {
       return {
-        accounts: this.$pgettext('*/Moderation/Title', "Accounts")
+        accounts: this.$pgettext('*/Moderation/Title', 'Accounts')
       }
     }
   }
diff --git a/front/src/views/admin/moderation/Base.vue b/front/src/views/admin/moderation/Base.vue
index 9bf5468b0..8d52dc717 100644
--- a/front/src/views/admin/moderation/Base.vue
+++ b/front/src/views/admin/moderation/Base.vue
@@ -1,31 +1,62 @@
 <template>
-  <div class="main pusher"  v-title="labels.moderation">
-    <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu">
+  <div
+    v-title="labels.moderation"
+    class="main pusher"
+  >
+    <nav
+      class="ui secondary pointing menu"
+      role="navigation"
+      :aria-label="labels.secondaryMenu"
+    >
       <router-link
         class="ui item"
-        :to="{name: 'manage.moderation.reports.list', query: {q: 'resolved:no'}}">
-        <translate translate-context="*/Moderation/*/Noun">Reports</translate>
+        :to="{name: 'manage.moderation.reports.list', query: {q: 'resolved:no'}}"
+      >
+        <translate translate-context="*/Moderation/*/Noun">
+          Reports
+        </translate>
         <div
           v-if="$store.state.ui.notifications.pendingReviewReports > 0"
-          :class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']">{{ $store.state.ui.notifications.pendingReviewReports }}</div>
+          :class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']"
+        >
+          {{ $store.state.ui.notifications.pendingReviewReports }}
+        </div>
       </router-link>
       <router-link
         class="ui item"
-        :to="{name: 'manage.moderation.requests.list', query: {q: 'status:pending'}}">
-        <translate translate-context="*/Moderation/*/Noun">User Requests</translate>
+        :to="{name: 'manage.moderation.requests.list', query: {q: 'status:pending'}}"
+      >
+        <translate translate-context="*/Moderation/*/Noun">
+          User Requests
+        </translate>
         <div
           v-if="$store.state.ui.notifications.pendingReviewRequests > 0"
-          :class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']">{{ $store.state.ui.notifications.pendingReviewRequests }}</div>
+          :class="['ui', 'circular', 'mini', 'right floated', 'accent', 'label']"
+        >
+          {{ $store.state.ui.notifications.pendingReviewRequests }}
+        </div>
       </router-link>
       <router-link
         class="ui item"
-        :to="{name: 'manage.moderation.domains.list'}"><translate translate-context="*/Moderation/*/Noun">Domains</translate></router-link>
+        :to="{name: 'manage.moderation.domains.list'}"
+      >
+        <translate translate-context="*/Moderation/*/Noun">
+          Domains
+        </translate>
+      </router-link>
       <router-link
         class="ui item"
-        :to="{name: 'manage.moderation.accounts.list'}"><translate translate-context="*/Moderation/Title">Accounts</translate></router-link>
-
+        :to="{name: 'manage.moderation.accounts.list'}"
+      >
+        <translate translate-context="*/Moderation/Title">
+          Accounts
+        </translate>
+      </router-link>
     </nav>
-    <router-view :allow-list-enabled="allowListEnabled" :key="$route.fullPath"></router-view>
+    <router-view
+      :key="$route.fullPath"
+      :allow-list-enabled="allowListEnabled"
+    />
   </div>
 </template>
 
@@ -39,24 +70,24 @@ export default {
       allowListEnabled: false
     }
   },
+  computed: {
+    labels () {
+      return {
+        moderation: this.$pgettext('*/Moderation/*', 'Moderation'),
+        secondaryMenu: this.$pgettext('Menu/*/Hidden text', 'Secondary menu')
+      }
+    }
+  },
   created () {
     this.fetchNodeInfo()
   },
   methods: {
     fetchNodeInfo () {
-      let self = this
+      const self = this
       axios.get('instance/nodeinfo/2.0/').then(response => {
         self.allowListEnabled = _.get(response.data, 'metadata.allowList.enabled', false)
       })
-    },
-  },
-  computed: {
-    labels() {
-      return {
-        moderation: this.$pgettext('*/Moderation/*', "Moderation"),
-        secondaryMenu: this.$pgettext('Menu/*/Hidden text', "Secondary menu")
-      }
     }
-  },
+  }
 }
 </script>
diff --git a/front/src/views/admin/moderation/DomainsDetail.vue b/front/src/views/admin/moderation/DomainsDetail.vue
index 38915b87d..17293512d 100644
--- a/front/src/views/admin/moderation/DomainsDetail.vue
+++ b/front/src/views/admin/moderation/DomainsDetail.vue
@@ -1,21 +1,32 @@
 <template>
   <main class="page-admin-domain-detail">
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object">
-      <section :class="['ui', 'head', 'vertical', 'stripe', 'segment']" v-title="object.name">
+      <section
+        v-title="object.name"
+        :class="['ui', 'head', 'vertical', 'stripe', 'segment']"
+      >
         <div class="ui stackable two column grid">
           <div class="ui column">
             <div class="segment-content">
               <h2 class="ui header">
-                <i class="circular inverted cloud icon"></i>
+                <i class="circular inverted cloud icon" />
                 <div class="content">
                   {{ object.name }}
                   <div class="sub header">
-                    <a :href="externalUrl" target="_blank" rel="noopener noreferrer" class="logo-wrapper">
+                    <a
+                      :href="externalUrl"
+                      target="_blank"
+                      rel="noopener noreferrer"
+                      class="logo-wrapper"
+                    >
                       <translate translate-context="Content/Moderation/Link/Verb">Open website</translate>&nbsp;
-                      <i class="external icon"></i>
+                      <i class="external icon" />
                     </a>
                   </div>
                 </div>
@@ -26,25 +37,36 @@
                     v-if="$store.state.auth.profile.is_superuser"
                     class="ui labeled icon button"
                     :href="$store.getters['instance/absoluteUrl'](`/api/admin/federation/domain/${object.name}`)"
-                    target="_blank" rel="noopener noreferrer">
-                    <i class="wrench icon"></i>
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >
+                    <i class="wrench icon" />
                     <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate>&nbsp;
                   </a>
                 </div>
-                <div v-if="allowListEnabled" class="ui icon buttons">
+                <div
+                  v-if="allowListEnabled"
+                  class="ui icon buttons"
+                >
                   <button
                     v-if="object.allowed"
+                    :class="['ui', 'labeled', {loading: isLoadingAllowList}, 'icon', 'button']"
                     @click.prevent="setAllowList(false)"
-                    :class="['ui', 'labeled', {loading: isLoadingAllowList}, 'icon', 'button']">
-                    <i class="x icon"></i>
-                    <translate translate-context="Content/Moderation/Action/Verb">Remove from allow-list</translate>
+                  >
+                    <i class="x icon" />
+                    <translate translate-context="Content/Moderation/Action/Verb">
+                      Remove from allow-list
+                    </translate>
                   </button>
                   <button
                     v-else
+                    :class="['ui', 'labeled', {loading: isLoadingAllowList}, 'icon', 'button']"
                     @click.prevent="setAllowList(true)"
-                    :class="['ui', 'labeled', {loading: isLoadingAllowList}, 'icon', 'button']">
-                    <i class="check icon"></i>
-                    <translate translate-context="Content/Moderation/Action/Verb">Add to allow-list</translate>
+                  >
+                    <i class="check icon" />
+                    <translate translate-context="Content/Moderation/Action/Verb">
+                      Add to allow-list
+                    </translate>
                   </button>
                 </div>
               </div>
@@ -54,38 +76,56 @@
             <div class="ui compact clearing placeholder segment component-placeholder">
               <template v-if="isLoadingPolicy">
                 <div class="paragraph">
-                  <div class="line"></div>
-                  <div class="line"></div>
-                  <div class="line"></div>
-                  <div class="line"></div>
-                  <div class="line"></div>
+                  <div class="line" />
+                  <div class="line" />
+                  <div class="line" />
+                  <div class="line" />
+                  <div class="line" />
                 </div>
               </template>
               <template v-else-if="!policy && !showPolicyForm">
                 <header class="ui header">
                   <h3>
-                    <i class="shield icon"></i>
-                    <translate translate-context="Content/Moderation/Card.Title">You don't have any rule in place for this domain.</translate>
+                    <i class="shield icon" />
+                    <translate translate-context="Content/Moderation/Card.Title">
+                      You don't have any rule in place for this domain.
+                    </translate>
                   </h3>
                 </header>
-                <p><translate translate-context="Content/Moderation/Card.Paragraph">Moderation policies help you control how your instance interact with a given domain or account.</translate></p>
-                <button @click="showPolicyForm = true" class="ui primary button">Add a moderation policy</button>
+                <p>
+                  <translate translate-context="Content/Moderation/Card.Paragraph">
+                    Moderation policies help you control how your instance interact with a given domain or account.
+                  </translate>
+                </p>
+                <button
+                  class="ui primary button"
+                  @click="showPolicyForm = true"
+                >
+                  Add a moderation policy
+                </button>
               </template>
-              <instance-policy-card v-else-if="policy && !showPolicyForm" :object="policy" @update="showPolicyForm = true">
+              <instance-policy-card
+                v-else-if="policy && !showPolicyForm"
+                :object="policy"
+                @update="showPolicyForm = true"
+              >
                 <header class="ui header">
                   <h3>
-                    <translate translate-context="Content/Moderation/Card.Title">This domain is subject to specific moderation rules</translate>
+                    <translate translate-context="Content/Moderation/Card.Title">
+                      This domain is subject to specific moderation rules
+                    </translate>
                   </h3>
                 </header>
               </instance-policy-card>
               <instance-policy-form
                 v-else-if="showPolicyForm"
+                :object="policy"
+                type="domain"
+                :target="object.name"
                 @cancel="showPolicyForm = false"
                 @save="updatePolicy"
                 @delete="policy = null; showPolicyForm = false"
-                :object="policy"
-                type="domain"
-                :target="object.name" />
+              />
             </div>
           </div>
         </div>
@@ -95,36 +135,62 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="info icon"></i>
+                <i class="info icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Instance data</translate>
+                  <translate translate-context="Content/Moderation/Title">
+                    Instance data
+                  </translate>
                 </div>
               </h3>
               <table class="ui very basic table">
                 <tbody>
                   <tr v-if="allowListEnabled">
                     <td>
-                      <translate translate-context="Content/Moderation/*/Adjective">Is present on allow-list</translate>
+                      <translate translate-context="Content/Moderation/*/Adjective">
+                        Is present on allow-list
+                      </translate>
                     </td>
                     <td>
-                      <translate v-if="object.allowed" translate-context="*/*/*">Yes</translate>
-                      <translate v-else translate-context="*/*/*">No</translate>
+                      <translate
+                        v-if="object.allowed"
+                        translate-context="*/*/*"
+                      >
+                        Yes
+                      </translate>
+                      <translate
+                        v-else
+                        translate-context="*/*/*"
+                      >
+                        No
+                      </translate>
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/*/Table.Label">Last checked</translate>
+                      <translate translate-context="Content/*/Table.Label">
+                        Last checked
+                      </translate>
                     </td>
                     <td>
-                      <human-date v-if="object.nodeinfo_fetch_date" :date="object.nodeinfo_fetch_date"></human-date>
-                      <translate v-else translate-context="*/*/*">N/A</translate>
+                      <human-date
+                        v-if="object.nodeinfo_fetch_date"
+                        :date="object.nodeinfo_fetch_date"
+                      />
+                      <translate
+                        v-else
+                        translate-context="*/*/*"
+                      >
+                        N/A
+                      </translate>
                     </td>
                   </tr>
 
                   <template v-if="object.nodeinfo && object.nodeinfo.status === 'ok'">
                     <tr>
                       <td>
-                        <translate translate-context="Content/Moderation/Table.Label">Software</translate>
+                        <translate translate-context="Content/Moderation/Table.Label">
+                          Software
+                        </translate>
                       </td>
                       <td>
                         {{ lodash.get(object, 'nodeinfo.payload.software.name', $pgettext('*/*/*', 'N/A')) }} ({{ lodash.get(object, 'nodeinfo.payload.software.version', $pgettext('*/*/*', 'N/A')) }})
@@ -132,7 +198,9 @@
                     </tr>
                     <tr>
                       <td>
-                        <translate translate-context="*/*/*/Noun">Name</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Name
+                        </translate>
                       </td>
                       <td>
                         {{ lodash.get(object, 'nodeinfo.payload.metadata.nodeName', $pgettext('*/*/*', 'N/A')) }}
@@ -140,7 +208,9 @@
                     </tr>
                     <tr>
                       <td>
-                        <translate translate-context="Content/*/*">Total users</translate>
+                        <translate translate-context="Content/*/*">
+                          Total users
+                        </translate>
                       </td>
                       <td>
                         {{ lodash.get(object, 'nodeinfo.payload.usage.users.total', $pgettext('*/*/*', 'N/A')) }}
@@ -150,55 +220,76 @@
                   <template v-if="object.nodeinfo && object.nodeinfo.status === 'error'">
                     <tr>
                       <td>
-                        <translate translate-context="*/*/*">Status</translate>
+                        <translate translate-context="*/*/*">
+                          Status
+                        </translate>
                       </td>
                       <td>
-                        <translate translate-context="Content/Moderation/Table">Error while fetching node info</translate>&nbsp;
+                        <translate translate-context="Content/Moderation/Table">
+                          Error while fetching node info
+                        </translate>&nbsp;
 
-                        <span :data-tooltip="object.nodeinfo.error"><i class="question circle icon"></i></span>
+                        <span :data-tooltip="object.nodeinfo.error"><i class="question circle icon" /></span>
                       </td>
                     </tr>
                   </template>
                 </tbody>
               </table>
-              <ajax-button @action-done="refreshNodeInfo" method="get" :url="'manage/federation/domains/' + object.name + '/nodeinfo/'">
-                <translate translate-context="Content/Moderation/Button.Label/Verb">Refresh node info</translate>
+              <ajax-button
+                method="get"
+                :url="'manage/federation/domains/' + object.name + '/nodeinfo/'"
+                @action-done="refreshNodeInfo"
+              >
+                <translate translate-context="Content/Moderation/Button.Label/Verb">
+                  Refresh node info
+                </translate>
               </ajax-button>
             </section>
           </div>
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="feed icon"></i>
+                <i class="feed icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Activity</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Activity
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">First seen</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Short (Value is a date)">
+                        First seen
+                      </translate>
                     </td>
                     <td>
-                      <human-date :date="object.creation_date"></human-date>
+                      <human-date :date="object.creation_date" />
                     </td>
                   </tr>
                   <tr>
                     <td>
                       <router-link
-                        :to="{name: 'manage.moderation.accounts.list', query: {q: 'domain:' + object.name }}">
-                        <translate translate-context="Content/Moderation/Table.Label.Link">Known accounts</translate>
-                        </router-link>
-
+                        :to="{name: 'manage.moderation.accounts.list', query: {q: 'domain:' + object.name }}"
+                      >
+                        <translate translate-context="Content/Moderation/Table.Label.Link">
+                          Known accounts
+                        </translate>
+                      </router-link>
                     </td>
                     <td>
                       {{ stats.actors }}
@@ -206,26 +297,32 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Emitted messages</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Emitted messages
+                      </translate>
                     </td>
                     <td>
-                      {{ stats.outbox_activities}}
+                      {{ stats.outbox_activities }}
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Received library follows</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Received library follows
+                      </translate>
                     </td>
                     <td>
-                      {{ stats.received_library_follows}}
+                      {{ stats.received_library_follows }}
                     </td>
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Emitted library follows</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Emitted library follows
+                      </translate>
                     </td>
                     <td>
-                      {{ stats.emitted_library_follows}}
+                      {{ stats.emitted_library_follows }}
                     </td>
                   </tr>
                 </tbody>
@@ -235,24 +332,33 @@
           <div class="column">
             <section>
               <h3 class="ui header">
-                <i class="music icon"></i>
+                <i class="music icon" />
                 <div class="content">
-                  <translate translate-context="Content/Moderation/Title">Audio content</translate>&nbsp;
-                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon"></i></span>
-
+                  <translate translate-context="Content/Moderation/Title">
+                    Audio content
+                  </translate>&nbsp;
+                  <span :data-tooltip="labels.statsWarning"><i class="question circle icon" /></span>
                 </div>
               </h3>
-              <div v-if="isLoadingStats" class="ui placeholder">
-                <div class="full line"></div>
-                <div class="short line"></div>
-                <div class="medium line"></div>
-                <div class="long line"></div>
+              <div
+                v-if="isLoadingStats"
+                class="ui placeholder"
+              >
+                <div class="full line" />
+                <div class="short line" />
+                <div class="medium line" />
+                <div class="long line" />
               </div>
-              <table v-else class="ui very basic table">
+              <table
+                v-else
+                class="ui very basic table"
+              >
                 <tbody>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label/Noun">Cached size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label/Noun">
+                        Cached size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_downloaded_size | humanSize }}
@@ -260,7 +366,9 @@
                   </tr>
                   <tr>
                     <td>
-                      <translate translate-context="Content/Moderation/Table.Label">Total size</translate>
+                      <translate translate-context="Content/Moderation/Table.Label">
+                        Total size
+                      </translate>
                     </td>
                     <td>
                       {{ stats.media_total_size | humanSize }}
@@ -269,7 +377,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.channels', query: {q: getQuery('domain', object.name) }}">
-                        <translate translate-context="*/*/*">Channels</translate>
+                        <translate translate-context="*/*/*">
+                          Channels
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -279,7 +389,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.libraries', query: {q: getQuery('domain', object.name) }}">
-                        <translate translate-context="*/*/*/Noun">Libraries</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Libraries
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -289,7 +401,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.uploads', query: {q: getQuery('domain', object.name) }}">
-                        <translate translate-context="*/*/*">Uploads</translate>
+                        <translate translate-context="*/*/*">
+                          Uploads
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -299,7 +413,9 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.artists', query: {q: getQuery('domain', object.name) }}">
-                        <translate translate-context="*/*/*/Noun">Artists</translate>
+                        <translate translate-context="*/*/*/Noun">
+                          Artists
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -309,17 +425,21 @@
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.albums', query: {q: getQuery('domain', object.name) }}">
-                        <translate translate-context="*/*/*">Albums</translate>
+                        <translate translate-context="*/*/*">
+                          Albums
+                        </translate>
                       </router-link>
                     </td>
                     <td>
-                      {{ stats.albums}}
+                      {{ stats.albums }}
                     </td>
                   </tr>
                   <tr>
                     <td>
                       <router-link :to="{name: 'manage.library.tracks', query: {q: getQuery('domain', object.name) }}">
-                        <translate translate-context="*/*/*">Tracks</translate>
+                        <translate translate-context="*/*/*">
+                          Tracks
+                        </translate>
                       </router-link>
                     </td>
                     <td>
@@ -328,31 +448,28 @@
                   </tr>
                 </tbody>
               </table>
-
             </section>
           </div>
         </div>
       </div>
-
     </template>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import logger from "@/logging"
+import axios from 'axios'
 import lodash from '@/lodash'
 
-import InstancePolicyForm from "@/components/manage/moderation/InstancePolicyForm"
-import InstancePolicyCard from "@/components/manage/moderation/InstancePolicyCard"
+import InstancePolicyForm from '@/components/manage/moderation/InstancePolicyForm'
+import InstancePolicyCard from '@/components/manage/moderation/InstancePolicyCard'
 
 export default {
-  props: ["id", "allowListEnabled"],
   components: {
     InstancePolicyForm,
-    InstancePolicyCard,
+    InstancePolicyCard
   },
-  data() {
+  props: { id: { type: Number, required: true }, allowListEnabled: { type: Boolean, required: true } },
+  data () {
     return {
       lodash,
       isLoading: true,
@@ -363,18 +480,28 @@ export default {
       object: null,
       stats: null,
       showPolicyForm: false,
-      permissions: [],
+      permissions: []
     }
   },
-  created() {
+  computed: {
+    labels () {
+      return {
+        statsWarning: this.$pgettext('Content/Moderation/Help text', 'Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain')
+      }
+    },
+    externalUrl () {
+      return `https://${this.object.name}`
+    }
+  },
+  created () {
     this.fetchData()
     this.fetchStats()
   },
   methods: {
-    fetchData() {
-      var self = this
+    fetchData () {
+      const self = this
       this.isLoading = true
-      let url = "manage/federation/domains/" + this.id + "/"
+      const url = 'manage/federation/domains/' + this.id + '/'
       axios.get(url).then(response => {
         self.object = response.data
         self.isLoading = false
@@ -383,29 +510,29 @@ export default {
         }
       })
     },
-    fetchStats() {
-      var self = this
+    fetchStats () {
+      const self = this
       this.isLoadingStats = true
-      let url = "manage/federation/domains/" + this.id + "/stats/"
+      const url = 'manage/federation/domains/' + this.id + '/stats/'
       axios.get(url).then(response => {
         self.stats = response.data
         self.isLoadingStats = false
       })
     },
-    fetchPolicy(id) {
-      var self = this
+    fetchPolicy (id) {
+      const self = this
       this.isLoadingPolicy = true
-      let url = `manage/moderation/instance-policies/${id}/`
+      const url = `manage/moderation/instance-policies/${id}/`
       axios.get(url).then(response => {
         self.policy = response.data
         self.isLoadingPolicy = false
       })
     },
-    setAllowList(value) {
-      var self = this
+    setAllowList (value) {
+      const self = this
       this.isLoadingAllowList = true
-      let url = `manage/federation/domains/${this.id}/`
-      axios.patch(url, {allowed: value}).then(response => {
+      const url = `manage/federation/domains/${this.id}/`
+      axios.patch(url, { allowed: value }).then(response => {
         self.object = response.data
         self.isLoadingAllowList = false
       })
@@ -421,16 +548,6 @@ export default {
     getQuery (field, value) {
       return `${field}:"${value}"`
     }
-  },
-  computed: {
-    labels() {
-      return {
-        statsWarning: this.$pgettext('Content/Moderation/Help text', "Statistics are computed from known activity and content on your instance, and do not reflect general activity for this domain")
-      }
-    },
-    externalUrl () {
-      return `https://${this.object.name}`
-    }
   }
 }
 </script>
diff --git a/front/src/views/admin/moderation/DomainsList.vue b/front/src/views/admin/moderation/DomainsList.vue
index 34bff4d92..0182961e9 100644
--- a/front/src/views/admin/moderation/DomainsList.vue
+++ b/front/src/views/admin/moderation/DomainsList.vue
@@ -1,32 +1,71 @@
 <template>
   <main v-title="labels.domains">
     <section class="ui vertical stripe segment">
-      <h2 class="ui left floated header"><translate translate-context="*/Moderation/*/Noun">Domains</translate></h2>
-      <form class="ui right floated form" @submit.prevent="createDomain">
-        <div v-if="errors && errors.length > 0" role="alert" class="ui negative message">
-          <h4 class="header"><translate translate-context="Content/Moderation/Message.Title">Error while creating domain</translate></h4>
+      <h2 class="ui left floated header">
+        <translate translate-context="*/Moderation/*/Noun">
+          Domains
+        </translate>
+      </h2>
+      <form
+        class="ui right floated form"
+        @submit.prevent="createDomain"
+      >
+        <div
+          v-if="errors && errors.length > 0"
+          role="alert"
+          class="ui negative message"
+        >
+          <h4 class="header">
+            <translate translate-context="Content/Moderation/Message.Title">
+              Error while creating domain
+            </translate>
+          </h4>
           <ul class="list">
-            <li v-for="error in errors">{{ error }}</li>
+            <li
+              v-for="(error, key) in errors"
+              :key="key"
+            >
+              {{ error }}
+            </li>
           </ul>
         </div>
         <div class="inline fields">
           <div class="field">
             <label for="add-domain"><translate translate-context="Content/Moderation/Form.Label/Verb">Add a domain</translate></label>
-            <input type="text" name="domain" id="add-domain" v-model="domainName">
+            <input
+              id="add-domain"
+              v-model="domainName"
+              type="text"
+              name="domain"
+            >
           </div>
-          <div class="field" v-if="allowListEnabled">
-            <input type="checkbox" name="allowed" id="allowed" v-model="domainAllowed">
+          <div
+            v-if="allowListEnabled"
+            class="field"
+          >
+            <input
+              id="allowed"
+              v-model="domainAllowed"
+              type="checkbox"
+              name="allowed"
+            >
             <label for="allowed"><translate translate-context="Content/Moderation/Action/Verb">Add to allow-list</translate></label>
           </div>
           <div class="field">
-            <button :class="['ui', {'loading': isCreating}, 'success', 'button']" type="submit" :disabled="isCreating">
-              <translate translate-context="Content/Moderation/Button/Verb">Add</translate>
+            <button
+              :class="['ui', {'loading': isCreating}, 'success', 'button']"
+              type="submit"
+              :disabled="isCreating"
+            >
+              <translate translate-context="Content/Moderation/Button/Verb">
+                Add
+              </translate>
             </button>
           </div>
         </div>
       </form>
-      <div class="ui clearing hidden divider"></div>
-      <domains-table :allow-list-enabled="allowListEnabled"></domains-table>
+      <div class="ui clearing hidden divider" />
+      <domains-table :allow-list-enabled="allowListEnabled" />
     </section>
   </main>
 </template>
@@ -34,12 +73,12 @@
 <script>
 import axios from 'axios'
 
-import DomainsTable from "@/components/manage/moderation/DomainsTable"
+import DomainsTable from '@/components/manage/moderation/DomainsTable'
 export default {
-  props: ['allowListEnabled'],
   components: {
     DomainsTable
   },
+  props: { allowListEnabled: { type: Boolean, required: true } },
   data () {
     return {
       domainName: '',
@@ -49,22 +88,22 @@ export default {
     }
   },
   computed: {
-    labels() {
+    labels () {
       return {
-        domains: this.$pgettext('*/Moderation/*/Noun', "Domains")
+        domains: this.$pgettext('*/Moderation/*/Noun', 'Domains')
       }
     }
   },
   methods: {
     createDomain () {
-      let self = this
+      const self = this
       this.isCreating = true
       this.errors = []
-      axios.post('manage/federation/domains/', {name: this.domainName, allowed: this.domainAllowed}).then((response) => {
+      axios.post('manage/federation/domains/', { name: this.domainName, allowed: this.domainAllowed }).then((response) => {
         this.isCreating = false
         this.$router.push({
-          name: "manage.moderation.domains.detail",
-          params: {'id': response.data.name}
+          name: 'manage.moderation.domains.detail',
+          params: { id: response.data.name }
         })
       }, (error) => {
         self.isCreating = false
diff --git a/front/src/views/admin/moderation/ReportDetail.vue b/front/src/views/admin/moderation/ReportDetail.vue
index ab2bd3e42..37bdbbac7 100644
--- a/front/src/views/admin/moderation/ReportDetail.vue
+++ b/front/src/views/admin/moderation/ReportDetail.vue
@@ -1,46 +1,48 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object">
-
       <div class="ui vertical stripe segment">
-        <report-card :obj="object"></report-card>
+        <report-card :obj="object" />
       </div>
     </template>
   </main>
 </template>
 
 <script>
-import axios from "axios"
+import axios from 'axios'
 
-import ReportCard from "@/components/manage/moderation/ReportCard"
+import ReportCard from '@/components/manage/moderation/ReportCard'
 
 export default {
-  props: ["id"],
   components: {
-    ReportCard,
+    ReportCard
   },
-  data() {
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       isLoading: true,
-      object: null,
+      object: null
     }
   },
-  created() {
+  created () {
     this.fetchData()
   },
   methods: {
-    fetchData() {
-      var self = this
+    fetchData () {
+      const self = this
       this.isLoading = true
-      let url = `manage/moderation/reports/${this.id}/`
+      const url = `manage/moderation/reports/${this.id}/`
       axios.get(url).then(response => {
         self.object = response.data
         self.isLoading = false
       })
-    },
-  },
+    }
+  }
 }
 </script>
diff --git a/front/src/views/admin/moderation/ReportsList.vue b/front/src/views/admin/moderation/ReportsList.vue
index 5660c01d5..54675070d 100644
--- a/front/src/views/admin/moderation/ReportsList.vue
+++ b/front/src/views/admin/moderation/ReportsList.vue
@@ -1,70 +1,124 @@
 <template>
   <main v-title="labels.reports">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header"><translate translate-context="*/Moderation/*/Noun">Reports</translate></h2>
-      <div class="ui hidden divider"></div>
+      <h2 class="ui header">
+        <translate translate-context="*/Moderation/*/Noun">
+          Reports
+        </translate>
+      </h2>
+      <div class="ui hidden divider" />
       <div class="ui inline form">
         <div class="fields">
           <div class="ui field">
             <label for="reports-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
             <form @submit.prevent="search.query = $refs.search.value">
-              <input id="reports-search" name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" />
+              <input
+                id="reports-search"
+                ref="search"
+                name="search"
+                type="text"
+                :value="search.query"
+                :placeholder="labels.searchPlaceholder"
+              >
             </form>
           </div>
           <div class="field">
             <label for="reports-status"><translate translate-context="*/*/*">Status</translate></label>
-            <select id="reports-status" class="ui dropdown" @change="addSearchToken('resolved', $event.target.value)" :value="getTokenValue('resolved', '')">
+            <select
+              id="reports-status"
+              class="ui dropdown"
+              :value="getTokenValue('resolved', '')"
+              @change="addSearchToken('resolved', $event.target.value)"
+            >
               <option value="">
-                <translate translate-context="Content/*/Dropdown">All</translate>
+                <translate translate-context="Content/*/Dropdown">
+                  All
+                </translate>
               </option>
               <option value="yes">
-                <translate translate-context="Content/*/*/Short">Resolved</translate>
+                <translate translate-context="Content/*/*/Short">
+                  Resolved
+                </translate>
               </option>
               <option value="no">
-                <translate translate-context="Content/*/*/Short">Unresolved</translate>
+                <translate translate-context="Content/*/*/Short">
+                  Unresolved
+                </translate>
               </option>
             </select>
           </div>
           <report-category-dropdown
             class="field"
-            @input="addSearchToken('category', $event)"
             :all="true"
             :label="true"
-            :value="getTokenValue('category', '')"></report-category-dropdown>
+            :value="getTokenValue('category', '')"
+            @input="addSearchToken('category', $event)"
+          />
           <div class="field">
             <label for="reports-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-            <select id="reports-ordering" class="ui dropdown" v-model="ordering">
-              <option v-for="option in orderingOptions" :value="option[0]">
+            <select
+              id="reports-ordering"
+              v-model="ordering"
+              class="ui dropdown"
+            >
+              <option
+                v-for="(option, key) in orderingOptions"
+                :key="key"
+                :value="option[0]"
+              >
                 {{ sharedLabels.filters[option[1]] }}
               </option>
             </select>
           </div>
           <div class="field">
             <label for="reports-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label>
-            <select id="reports-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-              <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-              <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+            <select
+              id="reports-ordering-direction"
+              v-model="orderingDirection"
+              class="ui dropdown"
+            >
+              <option value="+">
+                <translate translate-context="Content/Search/Dropdown">
+                  Ascending
+                </translate>
+              </option>
+              <option value="-">
+                <translate translate-context="Content/Search/Dropdown">
+                  Descending
+                </translate>
+              </option>
             </select>
           </div>
         </div>
       </div>
-      <div v-if="isLoading" class="ui active inverted dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <div v-else-if="!result || result.count === 0">
-        <empty-state @refresh="fetchData()" :refresh="true"></empty-state>
+        <empty-state
+          :refresh="true"
+          @refresh="fetchData()"
+        />
       </div>
       <div v-else-if="mode === 'card'">
-        <report-card @handled="fetchData" :obj="obj" v-for="obj in result.results" :key="obj.uuid" />
+        <report-card
+          v-for="obj in result.results"
+          :key="obj.uuid"
+          :obj="obj"
+          @handled="fetchData"
+        />
       </div>
       <div class="ui center aligned basic segment">
         <pagination
           v-if="result && result.count > paginateBy"
-          @page-changed="selectPage"
           :current="page"
           :paginate-by="paginateBy"
           :total="result.count"
-          ></pagination>
+          @page-changed="selectPage"
+        />
       </div>
     </section>
   </main>
@@ -72,7 +126,6 @@
 
 <script>
 
-
 import axios from 'axios'
 import _ from '@/lodash'
 import time from '@/utils/time'
@@ -81,22 +134,21 @@ import OrderingMixin from '@/components/mixins/Ordering'
 import TranslationsMixin from '@/components/mixins/Translations'
 import ReportCard from '@/components/manage/moderation/ReportCard'
 import ReportCategoryDropdown from '@/components/moderation/ReportCategoryDropdown'
-import {normalizeQuery, parseTokens} from '@/search'
+import { normalizeQuery, parseTokens } from '@/search'
 import SmartSearchMixin from '@/components/mixins/SmartSearch'
 
-
 export default {
-  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
   components: {
     Pagination,
     ReportCard,
-    ReportCategoryDropdown,
+    ReportCategoryDropdown
   },
+  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
   props: {
-    mode: {default: 'card'},
+    mode: { type: String, default: 'card' }
   },
   data () {
-    let defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
+    const defaultOrdering = this.getOrderingFromString(this.defaultOrdering || '-creation_date')
     return {
       time,
       isLoading: false,
@@ -111,72 +163,62 @@ export default {
       ordering: defaultOrdering.field,
       orderingOptions: [
         ['creation_date', 'creation_date'],
-        ['applied_date', 'applied_date'],
+        ['applied_date', 'applied_date']
       ],
       targets: {
         track: {}
       }
     }
   },
+  computed: {
+    labels () {
+      return {
+        searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by account, summary, domain…'),
+        reports: this.$pgettext('*/Moderation/*/Noun', 'Reports')
+      }
+    }
+  },
+  watch: {
+    search (newValue) {
+      this.page = 1
+      this.fetchData()
+    },
+    page () {
+      this.fetchData()
+    },
+    ordering () {
+      this.fetchData()
+    },
+    orderingDirection () {
+      this.fetchData()
+    }
+  },
   created () {
     this.fetchData()
   },
   methods: {
     fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search.query,
-        'ordering': this.getOrderingAsString()
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search.query,
+        ordering: this.getOrderingAsString()
       }, this.filters)
-      let self = this
+      const self = this
       self.isLoading = true
       this.result = null
-      axios.get('manage/moderation/reports/', {params: params}).then((response) => {
+      axios.get('manage/moderation/reports/', { params: params }).then((response) => {
         self.result = response.data
         self.isLoading = false
         if (self.search.query === 'resolved:no') {
           console.log('Refreshing sidebar notifications')
-          self.$store.commit('ui/incrementNotifications', {type: 'pendingReviewReports', value: response.data.count})
+          self.$store.commit('ui/incrementNotifications', { type: 'pendingReviewReports', value: response.data.count })
         }
       }, error => {
         self.isLoading = false
         self.errors = error.backendErrors
       })
     },
-    fetchTargets () {
-      // we request target data via the API so we can display previous state
-      // additionnal data next to the edit card
-      let self = this
-      let typesAndIds = {
-        track: {
-          url: 'tracks/',
-          ids: [],
-        }
-      }
-      this.result.results.forEach((m) => {
-        if (!m.target || !typesAndIds[m.target.type]) {
-          return
-        }
-        typesAndIds[m.target.type]['ids'].push(m.target.id)
-      })
-      Object.keys(typesAndIds).forEach((k) => {
-        let config = typesAndIds[k]
-        if (config.ids.length === 0) {
-          return
-        }
-        axios.get(config.url, {params: {id: _.uniq(config.ids), hidden: 'null'}}).then((response) => {
-          response.data.results.forEach((e) => {
-            self.$set(self.targets[k], e.id, {
-              payload: e,
-              currentState: edits.getCurrentStateForObj(e, edits.getConfigs.bind(self)()[k])
-            })
-          })
-        }, error => {
-          self.errors = error.backendErrors
-        })
-      })
-    },
     selectPage: function (page) {
       this.page = page
     },
@@ -200,29 +242,6 @@ export default {
       }
       return {}
     }
-  },
-  computed: {
-    labels () {
-      return {
-        searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by account, summary, domain…'),
-        reports: this.$pgettext('*/Moderation/*/Noun', "Reports"),
-      }
-    },
-  },
-  watch: {
-    search (newValue) {
-      this.page = 1
-      this.fetchData()
-    },
-    page () {
-      this.fetchData()
-    },
-    ordering () {
-      this.fetchData()
-    },
-    orderingDirection () {
-      this.fetchData()
-    }
   }
 }
 
diff --git a/front/src/views/admin/moderation/RequestDetail.vue b/front/src/views/admin/moderation/RequestDetail.vue
index b8f9e57c9..ca089ad8f 100644
--- a/front/src/views/admin/moderation/RequestDetail.vue
+++ b/front/src/views/admin/moderation/RequestDetail.vue
@@ -1,46 +1,48 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object">
-
       <div class="ui vertical stripe segment">
-        <user-request-card :obj="object"></user-request-card>
+        <user-request-card :obj="object" />
       </div>
     </template>
   </main>
 </template>
 
 <script>
-import axios from "axios"
+import axios from 'axios'
 
-import UserRequestCard from "@/components/manage/moderation/UserRequestCard"
+import UserRequestCard from '@/components/manage/moderation/UserRequestCard'
 
 export default {
-  props: ["id"],
   components: {
-    UserRequestCard,
+    UserRequestCard
   },
-  data() {
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       isLoading: true,
-      object: null,
+      object: null
     }
   },
-  created() {
+  created () {
     this.fetchData()
   },
   methods: {
-    fetchData() {
-      var self = this
+    fetchData () {
+      const self = this
       this.isLoading = true
-      let url = `manage/moderation/requests/${this.id}/`
+      const url = `manage/moderation/requests/${this.id}/`
       axios.get(url).then(response => {
         self.object = response.data
         self.isLoading = false
       })
-    },
-  },
+    }
+  }
 }
 </script>
diff --git a/front/src/views/admin/moderation/RequestsList.vue b/front/src/views/admin/moderation/RequestsList.vue
index 614694fd2..9e77b6340 100644
--- a/front/src/views/admin/moderation/RequestsList.vue
+++ b/front/src/views/admin/moderation/RequestsList.vue
@@ -1,68 +1,122 @@
 <template>
   <main v-title="labels.reports">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header"><translate translate-context="*/Moderation/*/Noun">User Requests</translate></h2>
-      <div class="ui hidden divider"></div>
+      <h2 class="ui header">
+        <translate translate-context="*/Moderation/*/Noun">
+          User Requests
+        </translate>
+      </h2>
+      <div class="ui hidden divider" />
       <div class="ui inline form">
         <div class="fields">
           <div class="ui field">
             <label for="requests-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
             <form @submit.prevent="search.query = $refs.search.value">
-              <input id="requests-search" name="search" ref="search" type="text" :value="search.query" :placeholder="labels.searchPlaceholder" />
+              <input
+                id="requests-search"
+                ref="search"
+                name="search"
+                type="text"
+                :value="search.query"
+                :placeholder="labels.searchPlaceholder"
+              >
             </form>
           </div>
           <div class="field">
             <label for="requests-status"><translate translate-context="*/*/*">Status</translate></label>
-            <select id="requests-status" class="ui dropdown" @change="addSearchToken('status', $event.target.value)" :value="getTokenValue('status', '')">
+            <select
+              id="requests-status"
+              class="ui dropdown"
+              :value="getTokenValue('status', '')"
+              @change="addSearchToken('status', $event.target.value)"
+            >
               <option value="">
-                <translate translate-context="Content/*/Dropdown">All</translate>
+                <translate translate-context="Content/*/Dropdown">
+                  All
+                </translate>
               </option>
               <option value="pending">
-                <translate translate-context="Content/Library/*/Short">Pending</translate>
+                <translate translate-context="Content/Library/*/Short">
+                  Pending
+                </translate>
               </option>
               <option value="approved">
-                <translate translate-context="Content/*/*/Short">Approved</translate>
+                <translate translate-context="Content/*/*/Short">
+                  Approved
+                </translate>
               </option>
               <option value="refused">
-                <translate translate-context="Content/*/*/Short">Refused</translate>
+                <translate translate-context="Content/*/*/Short">
+                  Refused
+                </translate>
               </option>
             </select>
           </div>
           <div class="field">
             <label for="requests-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-            <select id="requests-ordering" class="ui dropdown" v-model="ordering">
-              <option v-for="option in orderingOptions" :value="option[0]">
+            <select
+              id="requests-ordering"
+              v-model="ordering"
+              class="ui dropdown"
+            >
+              <option
+                v-for="(option, key) in orderingOptions"
+                :key="key"
+                :value="option[0]"
+              >
                 {{ sharedLabels.filters[option[1]] }}
               </option>
             </select>
           </div>
           <div class="field">
             <label for="requests-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label>
-            <select id="requests-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-              <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-              <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+            <select
+              id="requests-ordering-direction"
+              v-model="orderingDirection"
+              class="ui dropdown"
+            >
+              <option value="+">
+                <translate translate-context="Content/Search/Dropdown">
+                  Ascending
+                </translate>
+              </option>
+              <option value="-">
+                <translate translate-context="Content/Search/Dropdown">
+                  Descending
+                </translate>
+              </option>
             </select>
           </div>
         </div>
       </div>
-      <div v-if="isLoading" class="ui active inverted dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
       <div v-else-if="!result || result.count === 0">
-        <empty-state @refresh="fetchData()" :refresh="true"></empty-state>
+        <empty-state
+          :refresh="true"
+          @refresh="fetchData()"
+        />
       </div>
       <template v-else>
-        <user-request-card @handled="fetchData" :obj="obj" v-for="obj in result.results" :key="obj.uuid" />
+        <user-request-card
+          v-for="obj in result.results"
+          :key="obj.uuid"
+          :obj="obj"
+          @handled="fetchData"
+        />
         <div class="ui center aligned basic segment">
           <pagination
             v-if="result.count > paginateBy"
-            @page-changed="selectPage"
             :current="page"
             :paginate-by="paginateBy"
             :total="result.count"
-            ></pagination>
+            @page-changed="selectPage"
+          />
         </div>
-
       </template>
     </section>
   </main>
@@ -70,7 +124,6 @@
 
 <script>
 
-
 import axios from 'axios'
 import _ from '@/lodash'
 import time from '@/utils/time'
@@ -78,16 +131,15 @@ import Pagination from '@/components/Pagination'
 import OrderingMixin from '@/components/mixins/Ordering'
 import TranslationsMixin from '@/components/mixins/Translations'
 import UserRequestCard from '@/components/manage/moderation/UserRequestCard'
-import {normalizeQuery, parseTokens} from '@/search'
+import { normalizeQuery, parseTokens } from '@/search'
 import SmartSearchMixin from '@/components/mixins/SmartSearch'
 
-
 export default {
-  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
   components: {
     Pagination,
-    UserRequestCard,
+    UserRequestCard
   },
+  mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
   data () {
     return {
       time,
@@ -100,49 +152,20 @@ export default {
       },
       orderingOptions: [
         ['creation_date', 'creation_date'],
-        ['handled_date', 'handled_date'],
+        ['handled_date', 'handled_date']
       ],
       targets: {
         track: {}
       }
     }
   },
-  created () {
-    this.fetchData()
-  },
-  methods: {
-    fetchData () {
-      let params = _.merge({
-        'page': this.page,
-        'page_size': this.paginateBy,
-        'q': this.search.query,
-        'ordering': this.getOrderingAsString()
-      }, this.filters)
-      let self = this
-      self.isLoading = true
-      this.result = null
-      axios.get('manage/moderation/requests/', {params: params}).then((response) => {
-        self.result = response.data
-        self.isLoading = false
-        if (self.search.query === 'status:pending') {
-          self.$store.commit('ui/incrementNotifications', {type: 'pendingReviewRequests', value: response.data.count})
-        }
-      }, error => {
-        self.isLoading = false
-        self.errors = error.backendErrors
-      })
-    },
-    selectPage: function (page) {
-      this.page = page
-    },
-  },
   computed: {
     labels () {
       return {
         searchPlaceholder: this.$pgettext('Content/Search/Input.Placeholder', 'Search by username…'),
-        reports: this.$pgettext('*/Moderation/*/Noun', "User Requests"),
+        reports: this.$pgettext('*/Moderation/*/Noun', 'User Requests')
       }
-    },
+    }
   },
   watch: {
     search (newValue) {
@@ -158,6 +181,35 @@ export default {
     orderingDirection () {
       this.fetchData()
     }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      const params = _.merge({
+        page: this.page,
+        page_size: this.paginateBy,
+        q: this.search.query,
+        ordering: this.getOrderingAsString()
+      }, this.filters)
+      const self = this
+      self.isLoading = true
+      this.result = null
+      axios.get('manage/moderation/requests/', { params: params }).then((response) => {
+        self.result = response.data
+        self.isLoading = false
+        if (self.search.query === 'status:pending') {
+          self.$store.commit('ui/incrementNotifications', { type: 'pendingReviewRequests', value: response.data.count })
+        }
+      }, error => {
+        self.isLoading = false
+        self.errors = error.backendErrors
+      })
+    },
+    selectPage: function (page) {
+      this.page = page
+    }
   }
 }
 
diff --git a/front/src/views/admin/users/Base.vue b/front/src/views/admin/users/Base.vue
index 84d70bebb..96e9e44a5 100644
--- a/front/src/views/admin/users/Base.vue
+++ b/front/src/views/admin/users/Base.vue
@@ -1,24 +1,41 @@
 <template>
-  <div class="main pusher"  v-title="labels.manageUsers">
-    <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu">
+  <div
+    v-title="labels.manageUsers"
+    class="main pusher"
+  >
+    <nav
+      class="ui secondary pointing menu"
+      role="navigation"
+      :aria-label="labels.secondaryMenu"
+    >
       <router-link
         class="ui item"
-        :to="{name: 'manage.users.users.list'}"><translate translate-context="*/*/*/Noun">Users</translate></router-link>
+        :to="{name: 'manage.users.users.list'}"
+      >
+        <translate translate-context="*/*/*/Noun">
+          Users
+        </translate>
+      </router-link>
       <router-link
         class="ui item"
-        :to="{name: 'manage.users.invitations.list'}"><translate translate-context="*/Admin/*/Noun">Invitations</translate></router-link>
+        :to="{name: 'manage.users.invitations.list'}"
+      >
+        <translate translate-context="*/Admin/*/Noun">
+          Invitations
+        </translate>
+      </router-link>
     </nav>
-    <router-view :key="$route.fullPath"></router-view>
+    <router-view :key="$route.fullPath" />
   </div>
 </template>
 
 <script>
 export default {
   computed: {
-    labels() {
+    labels () {
       return {
         manageUsers: this.$pgettext('Head/Admin/Title', 'Manage users'),
-        secondaryMenu: this.$pgettext('Menu/*/Hidden text','Secondary menu')
+        secondaryMenu: this.$pgettext('Menu/*/Hidden text', 'Secondary menu')
       }
     }
   }
diff --git a/front/src/views/admin/users/InvitationsList.vue b/front/src/views/admin/users/InvitationsList.vue
index 96738983f..d95b387da 100644
--- a/front/src/views/admin/users/InvitationsList.vue
+++ b/front/src/views/admin/users/InvitationsList.vue
@@ -1,17 +1,19 @@
 <template>
   <main v-title="labels.invitations">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header">{{ labels.invitations }}</h2>
-      <invitation-form></invitation-form>
-      <div class="ui hidden divider"></div>
-      <invitations-table></invitations-table>
+      <h2 class="ui header">
+        {{ labels.invitations }}
+      </h2>
+      <invitation-form />
+      <div class="ui hidden divider" />
+      <invitations-table />
     </section>
   </main>
 </template>
 
 <script>
-import InvitationForm from "@/components/manage/users/InvitationForm"
-import InvitationsTable from "@/components/manage/users/InvitationsTable"
+import InvitationForm from '@/components/manage/users/InvitationForm'
+import InvitationsTable from '@/components/manage/users/InvitationsTable'
 
 export default {
   components: {
@@ -19,7 +21,7 @@ export default {
     InvitationsTable
   },
   computed: {
-    labels() {
+    labels () {
       return {
         invitations: this.$pgettext('*/Admin/*/Noun', 'Invitations')
       }
diff --git a/front/src/views/admin/users/UsersList.vue b/front/src/views/admin/users/UsersList.vue
index e54194ca1..29141850f 100644
--- a/front/src/views/admin/users/UsersList.vue
+++ b/front/src/views/admin/users/UsersList.vue
@@ -1,22 +1,24 @@
 <template>
   <main v-title="labels.users">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header">{{ labels.users }}</h2>
-      <div class="ui hidden divider"></div>
-      <users-table></users-table>
+      <h2 class="ui header">
+        {{ labels.users }}
+      </h2>
+      <div class="ui hidden divider" />
+      <users-table />
     </section>
   </main>
 </template>
 
 <script>
-import UsersTable from "@/components/manage/users/UsersTable"
+import UsersTable from '@/components/manage/users/UsersTable'
 
 export default {
   components: {
     UsersTable
   },
   computed: {
-    labels() {
+    labels () {
       return {
         users: this.$pgettext('*/*/*/Noun', 'Users')
       }
diff --git a/front/src/views/auth/Callback.vue b/front/src/views/auth/Callback.vue
index 4a64eb1e1..b16acae8c 100644
--- a/front/src/views/auth/Callback.vue
+++ b/front/src/views/auth/Callback.vue
@@ -2,10 +2,14 @@
   <main class="main pusher">
     <section class="ui vertical stripe segment">
       <div class="ui small text container">
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
         <div class="ui active inverted dimmer">
           <div class="ui text loader">
-            <h2><translate translate-context="*/Login/*">Logging in…</translate></h2>
+            <h2>
+              <translate translate-context="*/Login/*">
+                Logging in…
+              </translate>
+            </h2>
           </div>
         </div>
       </div>
@@ -16,7 +20,10 @@
 <script>
 
 export default {
-  props: ["state", "code"],
+  props: {
+    state: { type: String, required: true },
+    code: { type: String, required: true }
+  },
   async mounted () {
     await this.$store.dispatch('auth/handleOauthCallback', this.code)
     this.$router.push(this.state || '/library')
diff --git a/front/src/views/auth/EmailConfirm.vue b/front/src/views/auth/EmailConfirm.vue
index aec373d14..61952a274 100644
--- a/front/src/views/auth/EmailConfirm.vue
+++ b/front/src/views/auth/EmailConfirm.vue
@@ -1,30 +1,75 @@
 <template>
-  <main class="main pusher" v-title="labels.confirm">
+  <main
+    v-title="labels.confirm"
+    class="main pusher"
+  >
     <section class="ui vertical stripe segment">
       <div class="ui small text container">
         <h2>{{ labels.confirm }}</h2>
-        <form v-if="!success" class="ui form" @submit.prevent="submit()">
-          <div v-if="errors.length > 0" role="alert" class="ui negative message">
-            <h4 class="header"><translate translate-context="Content/Signup/Paragraph">Could not confirm your e-mail address</translate></h4>
+        <form
+          v-if="!success"
+          class="ui form"
+          @submit.prevent="submit()"
+        >
+          <div
+            v-if="errors.length > 0"
+            role="alert"
+            class="ui negative message"
+          >
+            <h4 class="header">
+              <translate translate-context="Content/Signup/Paragraph">
+                Could not confirm your e-mail address
+              </translate>
+            </h4>
             <ul class="list">
-              <li v-for="error in errors">{{ error }}</li>
+              <li
+                v-for="(error, i) in errors"
+                :key="i"
+              >
+                {{ error }}
+              </li>
             </ul>
           </div>
           <div class="field">
             <label for="confirmation-code"><translate translate-context="Content/Signup/Form.Label">Confirmation code</translate></label>
-            <input id="confirmation-code" name="confirmation-code" type="text" required v-model="key" />
+            <input
+              id="confirmation-code"
+              v-model="key"
+              name="confirmation-code"
+              type="text"
+              required
+            >
           </div>
           <router-link :to="{path: '/login'}">
-            <translate translate-context="Content/Signup/Link/Verb">Return to login</translate>
+            <translate translate-context="Content/Signup/Link/Verb">
+              Return to login
+            </translate>
           </router-link>
-          <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']" type="submit">
-            {{ labels.confirm }}</button>
+          <button
+            :class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
+            type="submit"
+          >
+            {{ labels.confirm }}
+          </button>
         </form>
-        <div v-else class="ui positive message">
-          <h4 class="header"><translate translate-context="Content/Signup/Message">E-mail address confirmed</translate></h4>
-          <p><translate translate-context="Content/Signup/Paragraph">You can now use the service without limitations.</translate></p>
+        <div
+          v-else
+          class="ui positive message"
+        >
+          <h4 class="header">
+            <translate translate-context="Content/Signup/Message">
+              E-mail address confirmed
+            </translate>
+          </h4>
+          <p>
+            <translate translate-context="Content/Signup/Paragraph">
+              You can now use the service without limitations.
+            </translate>
+          </p>
           <router-link :to="{name: 'login'}">
-            <translate translate-context="Content/Signup/Link/Verb">Proceed to login</translate>
+            <translate translate-context="Content/Signup/Link/Verb">
+              Proceed to login
+            </translate>
           </router-link>
         </div>
       </div>
@@ -33,11 +78,11 @@
 </template>
 
 <script>
-import axios from "axios"
+import axios from 'axios'
 
 export default {
-  props: ["defaultKey"],
-  data() {
+  props: { defaultKey: { type: String, required: true } },
+  data () {
     return {
       isLoading: false,
       errors: [],
@@ -46,9 +91,9 @@ export default {
     }
   },
   computed: {
-    labels() {
+    labels () {
       return {
-        confirm: this.$pgettext('Head/Signup/Title', "Confirm your e-mail address")
+        confirm: this.$pgettext('Head/Signup/Title', 'Confirm your e-mail address')
       }
     }
   },
@@ -58,14 +103,14 @@ export default {
     }
   },
   methods: {
-    submit() {
-      let self = this
+    submit () {
+      const self = this
       self.isLoading = true
       self.errors = []
-      let payload = {
+      const payload = {
         key: this.key
       }
-      return axios.post("auth/registration/verify-email/", payload).then(
+      return axios.post('auth/registration/verify-email/', payload).then(
         response => {
           self.isLoading = false
           self.success = true
diff --git a/front/src/views/auth/Login.vue b/front/src/views/auth/Login.vue
index a63716555..8938115bb 100644
--- a/front/src/views/auth/Login.vue
+++ b/front/src/views/auth/Login.vue
@@ -1,9 +1,16 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
     <section class="ui vertical stripe segment">
       <div class="ui small text container">
-        <h2><translate translate-context="Content/Login/Title/Verb">Log in to your Funkwhale account</translate></h2>
-        <login-form :next="redirectTo"></login-form>
+        <h2>
+          <translate translate-context="Content/Login/Title/Verb">
+            Log in to your Funkwhale account
+          </translate>
+        </h2>
+        <login-form :next="redirectTo" />
       </div>
     </section>
   </main>
@@ -13,6 +20,9 @@
 import LoginForm from '@/components/auth/LoginForm'
 
 export default {
+  components: {
+    LoginForm
+  },
   props: {
     next: { type: String, default: '/library' }
   },
@@ -21,8 +31,13 @@ export default {
       redirectTo: this.next
     }
   },
-  components: {
-    LoginForm
+  computed: {
+    labels () {
+      const title = this.$pgettext('Head/Login/Title', 'Log In')
+      return {
+        title
+      }
+    }
   },
   created () {
     const resolved = this.$router.resolve(this.redirectTo)
@@ -33,14 +48,6 @@ export default {
     if (this.$store.state.auth.authenticated) {
       this.$router.push(this.redirectTo)
     }
-  },
-  computed: {
-    labels () {
-      const title = this.$pgettext('Head/Login/Title', 'Log In')
-      return {
-        title
-      }
-    }
   }
 }
 </script>
diff --git a/front/src/views/auth/PasswordReset.vue b/front/src/views/auth/PasswordReset.vue
index 380e95a8f..47ea5490b 100644
--- a/front/src/views/auth/PasswordReset.vue
+++ b/front/src/views/auth/PasswordReset.vue
@@ -1,33 +1,69 @@
 <template>
-  <main class="main pusher" v-title="labels.reset">
+  <main
+    v-title="labels.reset"
+    class="main pusher"
+  >
     <section class="ui vertical stripe segment">
       <div class="ui small text container">
-        <h2><translate translate-context="*/Login/*/Verb">Reset your password</translate></h2>
-        <form class="ui form" @submit.prevent="submit()">
-          <div v-if="errors.length > 0" role="alert" class="ui negative message">
-            <h4 class="header"><translate translate-context="Content/Signup/Card.Title">Error while asking for a password reset</translate></h4>
+        <h2>
+          <translate translate-context="*/Login/*/Verb">
+            Reset your password
+          </translate>
+        </h2>
+        <form
+          class="ui form"
+          @submit.prevent="submit()"
+        >
+          <div
+            v-if="errors.length > 0"
+            role="alert"
+            class="ui negative message"
+          >
+            <h4 class="header">
+              <translate translate-context="Content/Signup/Card.Title">
+                Error while asking for a password reset
+              </translate>
+            </h4>
             <ul class="list">
-              <li v-for="error in errors">{{ error }}</li>
+              <li
+                v-for="(error, key) in errors"
+                :key="key"
+              >
+                {{ error }}
+              </li>
             </ul>
           </div>
-          <p><translate translate-context="Content/Signup/Paragraph">Use this form to request a password reset. We will send an e-mail to the given address with instructions to reset your password.</translate></p>
+          <p>
+            <translate translate-context="Content/Signup/Paragraph">
+              Use this form to request a password reset. We will send an e-mail to the given address with instructions to reset your password.
+            </translate>
+          </p>
           <div class="field">
             <label for="account-email"><translate translate-context="Content/Signup/Input.Label">Account's e-mail address</translate></label>
             <input
               id="account-email"
-              required
               ref="email"
+              v-model="email"
+              required
               type="email"
               name="email"
               autofocus
               :placeholder="labels.placeholder"
-              v-model="email">
+            >
           </div>
           <router-link :to="{path: '/login'}">
-            <translate translate-context="Content/Signup/Link">Back to login</translate>
+            <translate translate-context="Content/Signup/Link">
+              Back to login
+            </translate>
           </router-link>
-          <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']" type="submit">
-            <translate translate-context="Content/Signup/Button.Label/Verb">Ask for a password reset</translate></button>
+          <button
+            :class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
+            type="submit"
+          >
+            <translate translate-context="Content/Signup/Button.Label/Verb">
+              Ask for a password reset
+            </translate>
+          </button>
         </form>
       </div>
     </section>
@@ -35,24 +71,21 @@
 </template>
 
 <script>
-import axios from "axios"
+import axios from 'axios'
 
 export default {
-  props: ["defaultEmail"],
-  data() {
+  props: { defaultEmail: { type: String, required: true } },
+  data () {
     return {
       email: this.defaultEmail,
       isLoading: false,
       errors: []
     }
   },
-  mounted() {
-    this.$refs.email.focus()
-  },
   computed: {
-    labels() {
-      let reset = this.$pgettext('*/Login/*/Verb', "Reset your password")
-      let placeholder = this.$pgettext('Content/Signup/Input.Placeholder', "Enter the e-mail address linked to your account"
+    labels () {
+      const reset = this.$pgettext('*/Login/*/Verb', 'Reset your password')
+      const placeholder = this.$pgettext('Content/Signup/Input.Placeholder', 'Enter the e-mail address linked to your account'
       )
       return {
         reset,
@@ -60,19 +93,22 @@ export default {
       }
     }
   },
+  mounted () {
+    this.$refs.email.focus()
+  },
   methods: {
-    submit() {
-      let self = this
+    submit () {
+      const self = this
       self.isLoading = true
       self.errors = []
-      let payload = {
+      const payload = {
         email: this.email
       }
-      return axios.post("auth/password/reset/", payload).then(
+      return axios.post('auth/password/reset/', payload).then(
         response => {
           self.isLoading = false
           self.$router.push({
-            name: "auth.password-reset-confirm"
+            name: 'auth.password-reset-confirm'
           })
         },
         error => {
diff --git a/front/src/views/auth/PasswordResetConfirm.vue b/front/src/views/auth/PasswordResetConfirm.vue
index 7d0f4b84f..3653135a0 100644
--- a/front/src/views/auth/PasswordResetConfirm.vue
+++ b/front/src/views/auth/PasswordResetConfirm.vue
@@ -1,35 +1,83 @@
 <template>
-  <main class="main pusher" v-title="labels.changePassword">
+  <main
+    v-title="labels.changePassword"
+    class="main pusher"
+  >
     <section class="ui vertical stripe segment">
       <div class="ui small text container">
         <h2>{{ labels.changePassword }}</h2>
-        <form v-if="!success" class="ui form" @submit.prevent="submit()">
-          <div v-if="errors.length > 0" role="alert" class="ui negative message">
-            <h4 class="header"><translate translate-context="Content/Signup/Card.Title">Error while changing your password</translate></h4>
+        <form
+          v-if="!success"
+          class="ui form"
+          @submit.prevent="submit()"
+        >
+          <div
+            v-if="errors.length > 0"
+            role="alert"
+            class="ui negative message"
+          >
+            <h4 class="header">
+              <translate translate-context="Content/Signup/Card.Title">
+                Error while changing your password
+              </translate>
+            </h4>
             <ul class="list">
-              <li v-for="error in errors">{{ error }}</li>
+              <li
+                v-for="(error, key) in errors"
+                :key="key"
+              >
+                {{ error }}
+              </li>
             </ul>
           </div>
           <template v-if="token && uid">
             <div class="field">
               <label for="password-field"><translate translate-context="Content/Settings/Input.Label">New password</translate></label>
-              <password-input field-id="password-field" v-model="newPassword" />
+              <password-input
+                v-model="newPassword"
+                field-id="password-field"
+              />
             </div>
             <router-link :to="{path: '/login'}">
-              <translate translate-context="Content/Signup/Link">Back to login</translate>
+              <translate translate-context="Content/Signup/Link">
+                Back to login
+              </translate>
             </router-link>
-            <button :class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']" type="submit">
-              <translate translate-context="Content/Signup/Button.Label">Update your password</translate></button>
+            <button
+              :class="['ui', {'loading': isLoading}, 'right', 'floated', 'success', 'button']"
+              type="submit"
+            >
+              <translate translate-context="Content/Signup/Button.Label">
+                Update your password
+              </translate>
+            </button>
           </template>
           <template v-else>
-            <p><translate translate-context="Content/Signup/Paragraph">If the e-mail address provided in the previous step is valid and linked to a user account, you should receive an e-mail with reset instructions in the next couple of minutes.</translate></p>
+            <p>
+              <translate translate-context="Content/Signup/Paragraph">
+                If the e-mail address provided in the previous step is valid and linked to a user account, you should receive an e-mail with reset instructions in the next couple of minutes.
+              </translate>
+            </p>
           </template>
         </form>
-        <div v-else class="ui positive message">
-          <h4 class="header"><translate translate-context="Content/Signup/Card.Title">Password updated successfully</translate></h4>
-          <p><translate translate-context="Content/Signup/Card.Paragraph">Your password has been updated successfully.</translate></p>
+        <div
+          v-else
+          class="ui positive message"
+        >
+          <h4 class="header">
+            <translate translate-context="Content/Signup/Card.Title">
+              Password updated successfully
+            </translate>
+          </h4>
+          <p>
+            <translate translate-context="Content/Signup/Card.Paragraph">
+              Your password has been updated successfully.
+            </translate>
+          </p>
           <router-link :to="{name: 'login'}">
-            <translate translate-context="Content/Signup/Link/Verb">Proceed to login</translate>
+            <translate translate-context="Content/Signup/Link/Verb">
+              Proceed to login
+            </translate>
           </router-link>
         </div>
       </div>
@@ -38,17 +86,20 @@
 </template>
 
 <script>
-import axios from "axios"
-import PasswordInput from "@/components/forms/PasswordInput"
+import axios from 'axios'
+import PasswordInput from '@/components/forms/PasswordInput'
 
 export default {
-  props: ["defaultToken", "defaultUid"],
   components: {
     PasswordInput
   },
-  data() {
+  props: {
+    defaultToken: { type: String, required: true },
+    defaultUid: { type: String, required: true }
+  },
+  data () {
     return {
-      newPassword: "",
+      newPassword: '',
       isLoading: false,
       errors: [],
       token: this.defaultToken,
@@ -57,24 +108,24 @@ export default {
     }
   },
   computed: {
-    labels() {
+    labels () {
       return {
-        changePassword: this.$pgettext('*/Signup/Title', "Change your password")
+        changePassword: this.$pgettext('*/Signup/Title', 'Change your password')
       }
     }
   },
   methods: {
-    submit() {
-      let self = this
+    submit () {
+      const self = this
       self.isLoading = true
       self.errors = []
-      let payload = {
+      const payload = {
         uid: this.uid,
         token: this.token,
         new_password1: this.newPassword,
         new_password2: this.newPassword
       }
-      return axios.post("auth/password/reset/confirm/", payload).then(
+      return axios.post('auth/password/reset/confirm/', payload).then(
         response => {
           self.isLoading = false
           self.success = true
diff --git a/front/src/views/auth/Plugins.vue b/front/src/views/auth/Plugins.vue
index a03dc978a..6e6400ffd 100644
--- a/front/src/views/auth/Plugins.vue
+++ b/front/src/views/auth/Plugins.vue
@@ -1,19 +1,27 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
     <section class="ui vertical stripe segment">
       <div class="ui small text container">
         <h2>{{ labels.title }}</h2>
-        <div v-if="isLoading" class="ui inverted active dimmer">
-          <div class="ui loader"></div>
+        <div
+          v-if="isLoading"
+          class="ui inverted active dimmer"
+        >
+          <div class="ui loader" />
         </div>
 
-        <plugin-form
-          v-if="plugins && plugins.length > 0"
-          v-for="plugin in plugins"
-          :plugin="plugin"
-          :libraries="libraries"
-          :key="plugin.name"></plugin-form>
-        <empty-state v-else></empty-state>
+        <template v-if="plugins && plugins.length > 0">
+          <plugin-form
+            v-for="plugin in plugins"
+            :key="plugin.name"
+            :plugin="plugin"
+            :libraries="libraries"
+          />
+        </template>
+        <empty-state v-else />
       </div>
     </section>
   </main>
@@ -31,26 +39,26 @@ export default {
     return {
       isLoading: true,
       plugins: null,
-      libraries: null,
+      libraries: null
     }
   },
-  async created () {
-    await this.fetchData()
-  },
   computed: {
-    labels() {
-      let title = this.$pgettext('Head/Login/Title', "Manage plugins")
+    labels () {
+      const title = this.$pgettext('Head/Login/Title', 'Manage plugins')
       return {
         title
       }
     }
   },
+  async created () {
+    await this.fetchData()
+  },
   methods: {
     async fetchData () {
       this.isLoading = true
       let response = await axios.get('plugins')
       this.plugins = response.data
-      response = await axios.get('libraries', {paramis: {scope: 'me', page_size: 50}})
+      response = await axios.get('libraries', { paramis: { scope: 'me', page_size: 50 } })
       this.libraries = response.data.results
       this.isLoading = false
     }
diff --git a/front/src/views/auth/ProfileActivity.vue b/front/src/views/auth/ProfileActivity.vue
index d378547d6..433bd6570 100644
--- a/front/src/views/auth/ProfileActivity.vue
+++ b/front/src/views/auth/ProfileActivity.vue
@@ -1,48 +1,65 @@
 <template>
   <section>
     <div>
-      <radio-button v-if="recentActivity > 0" class="right floated" type="account" :object-id="{username: object.preferred_username, fullUsername: object.full_username}" :client-only="true"></radio-button>
+      <radio-button
+        v-if="recentActivity > 0"
+        class="right floated"
+        type="account"
+        :object-id="{username: object.preferred_username, fullUsername: object.full_username}"
+        :client-only="true"
+      />
       <h2 class="ui header">
-        <translate translate-context="Content/Home/Title">Recently listened</translate>
+        <translate translate-context="Content/Home/Title">
+          Recently listened
+        </translate>
       </h2>
-      <div class="ui divider"></div>
+      <div class="ui divider" />
       <track-widget
-        @count="recentActivity = $event"
         :url="'history/listenings/'"
-        :filters="{scope: `actor:${object.full_username}`, ordering: '-creation_date'}">
-      </track-widget>
+        :filters="{scope: `actor:${object.full_username}`, ordering: '-creation_date'}"
+        @count="recentActivity = $event"
+      />
     </div>
-    <div class="ui hidden divider"></div>
+    <div class="ui hidden divider" />
     <div>
       <h2 class="ui header">
-        <translate translate-context="Content/Home/Title">Recently favorited</translate>
+        <translate translate-context="Content/Home/Title">
+          Recently favorited
+        </translate>
       </h2>
-      <div class="ui divider"></div>
-      <track-widget :url="'favorites/tracks/'" :filters="{scope: `actor:${object.full_username}`, ordering: '-creation_date'}"></track-widget>
+      <div class="ui divider" />
+      <track-widget
+        :url="'favorites/tracks/'"
+        :filters="{scope: `actor:${object.full_username}`, ordering: '-creation_date'}"
+      />
     </div>
-    <div class="ui hidden divider"></div>
+    <div class="ui hidden divider" />
     <div>
       <h2 class="ui header">
-        <translate translate-context="*/*/*">Playlists</translate>
+        <translate translate-context="*/*/*">
+          Playlists
+        </translate>
       </h2>
-      <div class="ui divider"></div>
-      <playlist-widget :url="'playlists/'" :filters="{scope: `actor:${object.full_username}`, playable: true, ordering: '-modification_date'}">
-      </playlist-widget>
+      <div class="ui divider" />
+      <playlist-widget
+        :url="'playlists/'"
+        :filters="{scope: `actor:${object.full_username}`, playable: true, ordering: '-modification_date'}"
+      />
     </div>
   </section>
 </template>
 
 <script>
-import TrackWidget from "@/components/audio/track/Widget"
-import PlaylistWidget from "@/components/playlists/Widget"
-import RadioButton from "@/components/radios/Button"
+import TrackWidget from '@/components/audio/track/Widget'
+import PlaylistWidget from '@/components/playlists/Widget'
+import RadioButton from '@/components/radios/Button'
 
 export default {
-  props: ['object'],
-  components: {TrackWidget, PlaylistWidget, RadioButton},
+  components: { TrackWidget, PlaylistWidget, RadioButton },
+  props: { object: { type: Object, required: true } },
   data () {
     return {
-      recentActivity: 0,
+      recentActivity: 0
     }
   }
 }
diff --git a/front/src/views/auth/ProfileBase.vue b/front/src/views/auth/ProfileBase.vue
index f776322bf..bf525640c 100644
--- a/front/src/views/auth/ProfileBase.vue
+++ b/front/src/views/auth/ProfileBase.vue
@@ -1,79 +1,133 @@
 <template>
-  <main class="main pusher page-profile" v-title="labels.usernameProfile">
-    <div v-if="isLoading" class="ui vertical segment">
-      <div class="ui centered active inline loader"></div>
+  <main
+    v-title="labels.usernameProfile"
+    class="main pusher page-profile"
+  >
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div class="ui centered active inline loader" />
     </div>
     <div class="ui head vertical stripe segment container">
-      <div class="ui stackable grid" v-if="object">
+      <div
+        v-if="object"
+        class="ui stackable grid"
+      >
         <div class="ui five wide column">
-          <button class="ui pointing dropdown icon small basic right floated button" ref="dropdown" v-dropdown="{direction: 'downward'}" style="position: absolute; right: 1em; top: 1em;">
-            <i class="ellipsis vertical icon"></i>
+          <button
+            ref="dropdown"
+            v-dropdown="{direction: 'downward'}"
+            class="ui pointing dropdown icon small basic right floated button"
+            style="position: absolute; right: 1em; top: 1em;"
+          >
+            <i class="ellipsis vertical icon" />
             <div class="menu">
               <a
-                :href="object.fid"
                 v-if="object.domain != $store.getters['instance/domain']"
+                :href="object.fid"
                 target="_blank"
-                class="basic item">
-                <i class="external icon"></i>
-                <translate :translate-params="{domain: object.domain}" translate-context="Content/*/Button.Label/Verb">View on %{ domain }</translate>
+                class="basic item"
+              >
+                <i class="external icon" />
+                <translate
+                  :translate-params="{domain: object.domain}"
+                  translate-context="Content/*/Button.Label/Verb"
+                >View on %{ domain }</translate>
               </a>
               <div
-                role="button"
-                class="basic item"
                 v-for="obj in getReportableObjs({account: object})"
                 :key="obj.target.type + obj.target.id"
-                @click.stop.prevent="$store.dispatch('moderation/report', obj.target)">
+                role="button"
+                class="basic item"
+                @click.stop.prevent="$store.dispatch('moderation/report', obj.target)"
+              >
                 <i class="share icon" /> {{ obj.label }}
               </div>
 
-              <div class="divider"></div>
-              <router-link class="basic item" v-if="$store.state.auth.availablePermissions['moderation']" :to="{name: 'manage.moderation.accounts.detail', params: {id: object.full_username}}">
-                <i class="wrench icon"></i>
-                <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
+              <div class="divider" />
+              <router-link
+                v-if="$store.state.auth.availablePermissions['moderation']"
+                class="basic item"
+                :to="{name: 'manage.moderation.accounts.detail', params: {id: object.full_username}}"
+              >
+                <i class="wrench icon" />
+                <translate translate-context="Content/Moderation/Link">
+                  Open in moderation interface
+                </translate>
               </router-link>
             </div>
           </button>
           <h1 class="ui center aligned icon header">
-            <i v-if="!object.icon" class="circular inverted user success icon"></i>
-            <img alt="" class="ui big circular image" v-else v-lazy="$store.getters['instance/absoluteUrl'](object.icon.urls.medium_square_crop)" />
+            <i
+              v-if="!object.icon"
+              class="circular inverted user success icon"
+            />
+            <img
+              v-else
+              v-lazy="$store.getters['instance/absoluteUrl'](object.icon.urls.medium_square_crop)"
+              alt=""
+              class="ui big circular image"
+            >
             <div class="ellispsis content">
-              <div class="ui very small hidden divider"></div>
+              <div class="ui very small hidden divider" />
               <span>{{ displayName }}</span>
-              <div class="ui very small hidden divider"></div>
-              <div class="sub header ellipsis" :title="object.full_username">
+              <div class="ui very small hidden divider" />
+              <div
+                class="sub header ellipsis"
+                :title="object.full_username"
+              >
                 {{ object.full_username }}
               </div>
             </div>
-            <template  v-if="object.full_username === $store.state.auth.fullUsername">
-              <div class="ui very small hidden divider"></div>
+            <template v-if="object.full_username === $store.state.auth.fullUsername">
+              <div class="ui very small hidden divider" />
               <div class="ui basic success label">
-                <translate translate-context="Content/Profile/Button.Paragraph">This is you!</translate>
+                <translate translate-context="Content/Profile/Button.Paragraph">
+                  This is you!
+                </translate>
               </div>
             </template>
           </h1>
-          <div class="ui small hidden divider"></div>
+          <div class="ui small hidden divider" />
           <div v-if="$store.getters['ui/layoutVersion'] === 'large'">
             <rendered-description
-              @updated="$emit('updated', $event)"
               :content="object.summary"
               :field-name="'summary'"
               :update-url="`users/${$store.state.auth.username}/`"
-              :can-update="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername"></rendered-description>
+              :can-update="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername"
+              @updated="$emit('updated', $event)"
+            />
           </div>
         </div>
         <div class="ui eleven wide column">
           <div class="ui head vertical stripe segment">
             <div class="ui container">
               <div class="ui secondary pointing center aligned menu">
-                <router-link class="item" :exact="true" :to="{name: 'profile.overview', params: routerParams}">
-                  <translate translate-context="Content/Profile/Link">Overview</translate>
+                <router-link
+                  class="item"
+                  :exact="true"
+                  :to="{name: 'profile.overview', params: routerParams}"
+                >
+                  <translate translate-context="Content/Profile/Link">
+                    Overview
+                  </translate>
                 </router-link>
-                <router-link class="item" :exact="true" :to="{name: 'profile.activity', params: routerParams}">
-                  <translate translate-context="Content/Profile/*">Activity</translate>
+                <router-link
+                  class="item"
+                  :exact="true"
+                  :to="{name: 'profile.activity', params: routerParams}"
+                >
+                  <translate translate-context="Content/Profile/*">
+                    Activity
+                  </translate>
                 </router-link>
               </div>
-              <div class="ui hidden divider"></div>
-              <router-view @updated="fetch" :object="object"></router-view>
+              <div class="ui hidden divider" />
+              <router-view
+                :object="object"
+                @updated="fetch"
+              />
             </div>
           </div>
         </div>
@@ -83,50 +137,30 @@
 </template>
 
 <script>
-import { mapState } from "vuex"
 import axios from 'axios'
 
 import ReportMixin from '@/components/mixins/Report'
 
 export default {
   mixins: [ReportMixin],
+  beforeRouteUpdate (to, from, next) {
+    to.meta.preserveScrollPosition = true
+    next()
+  },
   props: {
-    username: {type: String, required: true},
-    domain: {type: String, required: false, default: null},
+    username: { type: String, required: true },
+    domain: { type: String, required: false, default: null }
   },
   data () {
     return {
       object: null,
-      isLoading: false,
-    }
-  },
-  created() {
-    let authenticated = this.$store.state.auth.authenticated
-    if (!authenticated && this.domain && this.$store.getters['instance/domain'] != this.domain) {
-      this.$router.push({name: 'login', query: {next: this.$route.fullPath}})
-    } else {
-     this.fetch()
-    }
-  },
-  beforeRouteUpdate (to, from, next) {
-    to.meta.preserveScrollPosition = true
-    next()
-  },
-  methods: {
-    fetch () {
-      let self = this
-      self.object = null
-      self.isLoading = true
-      axios.get(`federation/actors/${this.fullUsername}/`).then((response) => {
-        self.object = response.data
-        self.isLoading = false
-      })
+      isLoading: false
     }
   },
   computed: {
-    labels() {
-      let msg = this.$pgettext('Head/Profile/Title', "%{ username }'s profile")
-      let usernameProfile = this.$gettextInterpolate(msg, {
+    labels () {
+      const msg = this.$pgettext('Head/Profile/Title', "%{ username }'s profile")
+      const usernameProfile = this.$gettextInterpolate(msg, {
         username: this.username
       })
       return {
@@ -142,9 +176,9 @@ export default {
     },
     routerParams () {
       if (this.domain) {
-        return {username: this.username, domain: this.domain}
+        return { username: this.username, domain: this.domain }
       } else {
-        return {username: this.username}
+        return { username: this.username }
       }
     },
     displayName () {
@@ -158,6 +192,25 @@ export default {
     username () {
       this.fetch()
     }
+  },
+  created () {
+    const authenticated = this.$store.state.auth.authenticated
+    if (!authenticated && this.domain && this.$store.getters['instance/domain'] !== this.domain) {
+      this.$router.push({ name: 'login', query: { next: this.$route.fullPath } })
+    } else {
+      this.fetch()
+    }
+  },
+  methods: {
+    fetch () {
+      const self = this
+      self.object = null
+      self.isLoading = true
+      axios.get(`federation/actors/${this.fullUsername}/`).then((response) => {
+        self.object = response.data
+        self.isLoading = false
+      })
+    }
   }
 }
 </script>
diff --git a/front/src/views/auth/ProfileOverview.vue b/front/src/views/auth/ProfileOverview.vue
index 3c0f89eca..43c1428f2 100644
--- a/front/src/views/auth/ProfileOverview.vue
+++ b/front/src/views/auth/ProfileOverview.vue
@@ -2,45 +2,87 @@
   <section>
     <div v-if="$store.getters['ui/layoutVersion'] === 'small'">
       <rendered-description
-        @updated="$emit('updated', $event)"
         :content="object.summary"
         :field-name="'summary'"
         :update-url="`users/${$store.state.auth.username}/`"
-        :can-update="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername"></rendered-description>
-      <div class="ui hidden divider"></div>
+        :can-update="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername"
+        @updated="$emit('updated', $event)"
+      />
+      <div class="ui hidden divider" />
     </div>
     <div>
       <h2 class="ui with-actions header">
-        <translate translate-context="*/*/*">Channels</translate>
-        <div class="actions" v-if="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername">
-          <a @click.stop.prevent="showCreateModal = true" href="">
-            <i class="plus icon"></i>
+        <translate translate-context="*/*/*">
+          Channels
+        </translate>
+        <div
+          v-if="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername"
+          class="actions"
+        >
+          <a
+            href=""
+            @click.stop.prevent="showCreateModal = true"
+          >
+            <i class="plus icon" />
             <translate translate-context="Content/Profile/Button">Add new</translate>
           </a>
         </div>
       </h2>
-      <channels-widget :filters="{scope: `actor:${object.full_username}`}"></channels-widget>
+      <channels-widget :filters="{scope: `actor:${object.full_username}`}" />
       <h2 class="ui with-actions header">
-        <translate translate-context="Content/Profile/Header">User Libraries</translate>
-        <div class="actions" v-if="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername">
+        <translate translate-context="Content/Profile/Header">
+          User Libraries
+        </translate>
+        <div
+          v-if="$store.state.auth.authenticated && object.full_username === $store.state.auth.fullUsername"
+          class="actions"
+        >
           <router-link :to="{name: 'content.libraries.index'}">
-            <i class="plus icon"></i>
-            <translate translate-context="Content/Profile/Button">Add new</translate>
+            <i class="plus icon" />
+            <translate translate-context="Content/Profile/Button">
+              Add new
+            </translate>
           </router-link>
         </div>
       </h2>
       <library-widget :url="`federation/actors/${object.full_username}/libraries/`">
-        <translate translate-context="Content/Profile/Paragraph" slot="title">This user shared the following libraries</translate>
+        <translate
+          slot="title"
+          translate-context="Content/Profile/Paragraph"
+        >
+          This user shared the following libraries
+        </translate>
       </library-widget>
     </div>
 
     <modal :show.sync="showCreateModal">
       <h4 class="header">
-        <translate v-if="step === 1" key="1" translate-context="Content/Channel/*/Verb">Create channel</translate>
-        <translate v-else-if="category === 'podcast'" key="2" translate-context="Content/Channel/*">Podcast channel</translate>
-        <translate v-else key="3" translate-context="Content/Channel/*">Artist channel</translate>
+        <translate
+          v-if="step === 1"
+          key="1"
+          translate-context="Content/Channel/*/Verb"
+        >
+          Create channel
+        </translate>
+        <translate
+          v-else-if="category === 'podcast'"
+          key="2"
+          translate-context="Content/Channel/*"
+        >
+          Podcast channel
+        </translate>
+        <translate
+          v-else
+          key="3"
+          translate-context="Content/Channel/*"
+        >
+          Artist channel
+        </translate>
       </h4>
-      <div class="scrolling content" ref="modalContent">
+      <div
+        ref="modalContent"
+        class="scrolling content"
+      >
         <channel-form
           ref="createForm"
           :object="null"
@@ -49,44 +91,69 @@
           @submittable="submittable = $event"
           @category="category = $event"
           @errored="$refs.modalContent.scrollTop = 0"
-          @created="$router.push({name: 'channels.detail', params: {id: $event.actor.preferred_username}})"></channel-form>
-          <div class="ui hidden divider"></div>
+          @created="$router.push({name: 'channels.detail', params: {id: $event.actor.preferred_username}})"
+        />
+        <div class="ui hidden divider" />
       </div>
       <div class="actions">
-        <button v-if="step === 1" class="ui basic deny button">
-          <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+        <button
+          v-if="step === 1"
+          class="ui basic deny button"
+        >
+          <translate translate-context="*/*/Button.Label/Verb">
+            Cancel
+          </translate>
         </button>
-        <button v-if="step > 1" class="ui basic button" @click.stop.prevent="step -= 1">
-          <translate translate-context="*/*/Button.Label/Verb">Previous step</translate>
+        <button
+          v-if="step > 1"
+          class="ui basic button"
+          @click.stop.prevent="step -= 1"
+        >
+          <translate translate-context="*/*/Button.Label/Verb">
+            Previous step
+          </translate>
         </button>
-        <button v-if="step === 1" class="ui primary button" @click.stop.prevent="step += 1">
-          <translate translate-context="*/*/Button.Label">Next step</translate>
+        <button
+          v-if="step === 1"
+          class="ui primary button"
+          @click.stop.prevent="step += 1"
+        >
+          <translate translate-context="*/*/Button.Label">
+            Next step
+          </translate>
         </button>
-        <button v-if="step === 2" :class="['ui', 'primary button', {loading: isLoading}]" type="submit" @click.prevent.stop="$refs.createForm.submit" :disabled="!submittable && !isLoading">
-          <translate translate-context="*/Channels/Button.Label">Create channel</translate>
+        <button
+          v-if="step === 2"
+          :class="['ui', 'primary button', {loading: isLoading}]"
+          type="submit"
+          :disabled="!submittable && !isLoading"
+          @click.prevent.stop="$refs.createForm.submit"
+        >
+          <translate translate-context="*/Channels/Button.Label">
+            Create channel
+          </translate>
         </button>
       </div>
     </modal>
-
   </section>
 </template>
 
 <script>
 import Modal from '@/components/semantic/Modal'
-import LibraryWidget from "@/components/federation/LibraryWidget"
-import ChannelsWidget from "@/components/audio/ChannelsWidget"
-import ChannelForm from "@/components/audio/ChannelForm"
+import LibraryWidget from '@/components/federation/LibraryWidget'
+import ChannelsWidget from '@/components/audio/ChannelsWidget'
+import ChannelForm from '@/components/audio/ChannelForm'
 
 export default {
-  props: ['object'],
-  components: {ChannelsWidget, LibraryWidget, ChannelForm, Modal},
+  components: { ChannelsWidget, LibraryWidget, ChannelForm, Modal },
+  props: { object: { type: Object, required: true } },
   data () {
     return {
       showCreateModal: false,
       isLoading: false,
       submittable: false,
       step: 1,
-      category: 'podcast',
+      category: 'podcast'
     }
   }
 }
diff --git a/front/src/views/auth/Signup.vue b/front/src/views/auth/Signup.vue
index 0c77f4daf..605658727 100644
--- a/front/src/views/auth/Signup.vue
+++ b/front/src/views/auth/Signup.vue
@@ -1,9 +1,19 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
     <section class="ui vertical stripe segment">
       <div class="ui small text container">
-        <h2><translate translate-context="Content/Signup/Title">Create a Funkwhale account</translate></h2>
-        <signup-form :default-invitation="defaultInvitation" :next="next"></signup-form>
+        <h2>
+          <translate translate-context="Content/Signup/Title">
+            Create a Funkwhale account
+          </translate>
+        </h2>
+        <signup-form
+          :default-invitation="defaultInvitation"
+          :next="next"
+        />
       </div>
     </section>
   </main>
@@ -11,21 +21,21 @@
 
 <script>
 
-import SignupForm from "@/components/auth/SignupForm"
+import SignupForm from '@/components/auth/SignupForm'
 
 export default {
-  props: {
-    defaultInvitation: { type: String, required: false, default: null },
-    next: { type: String, default: "/" }
-  },
   components: {
     SignupForm
   },
-  data() {
+  props: {
+    defaultInvitation: { type: String, required: false, default: null },
+    next: { type: String, default: '/' }
+  },
+  data () {
     return {
-      username: "",
-      email: "",
-      password: "",
+      username: '',
+      email: '',
+      password: '',
       isLoadingInstanceSetting: true,
       errors: [],
       isLoading: false,
@@ -33,12 +43,12 @@ export default {
     }
   },
   computed: {
-    labels() {
-      let title = this.$pgettext("*/Signup/Title", "Sign Up")
+    labels () {
+      const title = this.$pgettext('*/Signup/Title', 'Sign Up')
       return {
         title
       }
     }
-  },
+  }
 }
 </script>
diff --git a/front/src/views/channels/DetailBase.vue b/front/src/views/channels/DetailBase.vue
index 76506cacf..3d878620a 100644
--- a/front/src/views/channels/DetailBase.vue
+++ b/front/src/views/channels/DetailBase.vue
@@ -1,132 +1,240 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
-    <div v-if="isLoading" class="ui vertical segment">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
+    <div
+      v-if="isLoading"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
     <template v-if="object && !isLoading">
-      <section class="ui head vertical stripe segment container" v-title="object.artist.name">
+      <section
+        v-title="object.artist.name"
+        class="ui head vertical stripe segment container"
+      >
         <div class="ui stackable grid">
           <div class="seven wide column">
             <div class="ui two column grid">
               <div class="column">
-                <img alt="" class="huge channel-image" v-if="object.artist.cover" :src="$store.getters['instance/absoluteUrl'](object.artist.cover.urls.medium_square_crop)">
-                <i v-else class="huge circular inverted users violet icon"></i>
+                <img
+                  v-if="object.artist.cover"
+                  alt=""
+                  class="huge channel-image"
+                  :src="$store.getters['instance/absoluteUrl'](object.artist.cover.urls.medium_square_crop)"
+                >
+                <i
+                  v-else
+                  class="huge circular inverted users violet icon"
+                />
               </div>
               <div class="ui column right aligned">
-                <tags-list v-if="object.artist.tags && object.artist.tags.length > 0" :tags="object.artist.tags"></tags-list>
-                <actor-link v-if="object.actor" :avatar="false" :actor="object.attributed_to" :display-name="true"></actor-link>
+                <tags-list
+                  v-if="object.artist.tags && object.artist.tags.length > 0"
+                  :tags="object.artist.tags"
+                />
+                <actor-link
+                  v-if="object.actor"
+                  :avatar="false"
+                  :actor="object.attributed_to"
+                  :display-name="true"
+                />
                 <template v-if="totalTracks > 0">
-                  <div class="ui hidden very small divider"></div>
-                  <translate translate-context="Content/Channel/Paragraph"
-                    key="1"
+                  <div class="ui hidden very small divider" />
+                  <translate
                     v-if="object.artist.content_category === 'podcast'"
+                    key="1"
+                    translate-context="Content/Channel/Paragraph"
                     translate-plural="%{ count } episodes"
                     :translate-n="totalTracks"
-                    :translate-params="{count: totalTracks}">
+                    :translate-params="{count: totalTracks}"
+                  >
                     %{ count } episode
                   </translate>
-                  <translate key="2" v-else translate-context="*/*/*" :translate-params="{count: totalTracks}" :translate-n="totalTracks" translate-plural="%{ count } tracks">%{ count } track</translate>
+                  <translate
+                    v-else
+                    key="2"
+                    translate-context="*/*/*"
+                    :translate-params="{count: totalTracks}"
+                    :translate-n="totalTracks"
+                    translate-plural="%{ count } tracks"
+                  >
+                    %{ count } track
+                  </translate>
                 </template>
                 <template v-if="object.attributed_to.full_username === $store.state.auth.fullUsername || $store.getters['channels/isSubscribed'](object.uuid)">
-                  <br><translate translate-context="Content/Channel/Paragraph" translate-plural="%{ count } subscribers" :translate-n="object.subscriptions_count" :translate-params="{count: object.subscriptions_count}">%{ count } subscriber</translate>
-                  <br><translate translate-context="Content/Channel/Paragraph" translate-plural="%{ count } listenings" :translate-n="object.downloads_count" :translate-params="{count: object.downloads_count}">%{ count } listening</translate>
+                  <br><translate
+                    translate-context="Content/Channel/Paragraph"
+                    translate-plural="%{ count } subscribers"
+                    :translate-n="object.subscriptions_count"
+                    :translate-params="{count: object.subscriptions_count}"
+                  >
+                    %{ count } subscriber
+                  </translate>
+                  <br><translate
+                    translate-context="Content/Channel/Paragraph"
+                    translate-plural="%{ count } listenings"
+                    :translate-n="object.downloads_count"
+                    :translate-params="{count: object.downloads_count}"
+                  >
+                    %{ count } listening
+                  </translate>
                 </template>
-                <div class="ui hidden small divider"></div>
-                <a @click.stop.prevent="showSubscribeModal = true" class="ui icon small basic button">
-                  <i class="feed icon"></i>
+                <div class="ui hidden small divider" />
+                <a
+                  class="ui icon small basic button"
+                  @click.stop.prevent="showSubscribeModal = true"
+                >
+                  <i class="feed icon" />
                 </a>
-                <modal class="tiny" :show.sync="showSubscribeModal">
+                <modal
+                  class="tiny"
+                  :show.sync="showSubscribeModal"
+                >
                   <h4 class="header">
-                    <translate translate-context="Popup/Channel/Title/Verb">Subscribe to this channel</translate>
+                    <translate translate-context="Popup/Channel/Title/Verb">
+                      Subscribe to this channel
+                    </translate>
                   </h4>
                   <div class="scrollable content">
                     <div class="description">
-
                       <template v-if="$store.state.auth.authenticated">
                         <h3>
-                          <i class="user icon"></i>
-                          <translate translate-context="Content/Channels/Header">Subscribe on Funkwhale</translate>
+                          <i class="user icon" />
+                          <translate translate-context="Content/Channels/Header">
+                            Subscribe on Funkwhale
+                          </translate>
                         </h3>
-                        <subscribe-button @subscribed="object.subscriptions_count += 1" @unsubscribed="object.subscriptions_count -= 1" :channel="object"></subscribe-button>
+                        <subscribe-button
+                          :channel="object"
+                          @subscribed="object.subscriptions_count += 1"
+                          @unsubscribed="object.subscriptions_count -= 1"
+                        />
                       </template>
                       <template v-if="object.rss_url">
                         <h3>
-                          <i class="feed icon"></i>
-                          <translate translate-context="Content/Channels/Header">Subscribe via RSS</translate>
+                          <i class="feed icon" />
+                          <translate translate-context="Content/Channels/Header">
+                            Subscribe via RSS
+                          </translate>
                         </h3>
-                        <p><translate translate-context="Content/Channels/Label">Copy-paste the following URL in your favorite podcatcher:</translate></p>
+                        <p>
+                          <translate translate-context="Content/Channels/Label">
+                            Copy-paste the following URL in your favorite podcatcher:
+                          </translate>
+                        </p>
                         <copy-input :value="object.rss_url" />
                       </template>
                       <template v-if="object.actor">
                         <h3>
-                          <i class="bell icon"></i>
-                          <translate translate-context="Content/Channels/Header">Subscribe on the Fediverse</translate>
+                          <i class="bell icon" />
+                          <translate translate-context="Content/Channels/Header">
+                            Subscribe on the Fediverse
+                          </translate>
                         </h3>
-                        <p><translate translate-context="Content/Channels/Label">If you're using Mastodon or other fediverse applications, you can subscribe to this account:</translate></p>
+                        <p>
+                          <translate translate-context="Content/Channels/Label">
+                            If you're using Mastodon or other fediverse applications, you can subscribe to this account:
+                          </translate>
+                        </p>
                         <copy-input :value="`@${object.actor.full_username}`" />
                       </template>
                     </div>
                   </div>
                   <div class="actions">
                     <button class="ui basic deny button">
-                      <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+                      <translate translate-context="*/*/Button.Label/Verb">
+                        Cancel
+                      </translate>
                     </button>
                   </div>
                 </modal>
-                <button class="ui right floated pointing dropdown icon small basic button" ref="dropdown" v-dropdown="{direction: 'downward'}">
-                  <i class="ellipsis vertical icon"></i>
+                <button
+                  ref="dropdown"
+                  v-dropdown="{direction: 'downward'}"
+                  class="ui right floated pointing dropdown icon small basic button"
+                >
+                  <i class="ellipsis vertical icon" />
                   <div class="menu">
                     <a
-                      href=""
                       v-if="totalTracks > 0"
+                      href=""
+                      class="basic item"
                       @click.prevent="showEmbedModal = !showEmbedModal"
-                      class="basic item">
-                      <i class="code icon"></i>
+                    >
+                      <i class="code icon" />
                       <translate translate-context="Content/*/Button.Label/Verb">Embed</translate>
                     </a>
                     <a
-                      :href="object.url"
                       v-if="object.actor && object.actor.domain != $store.getters['instance/domain']"
+                      :href="object.url"
                       target="_blank"
-                      class="basic item">
-                      <i class="external icon"></i>
-                      <translate :translate-params="{domain: object.actor.domain}" translate-context="Content/*/Button.Label/Verb">View on %{ domain }</translate>
+                      class="basic item"
+                    >
+                      <i class="external icon" />
+                      <translate
+                        :translate-params="{domain: object.actor.domain}"
+                        translate-context="Content/*/Button.Label/Verb"
+                      >View on %{ domain }</translate>
                     </a>
-                    <div class="divider"></div>
+                    <div class="divider" />
                     <a
-                      href=""
-                      class="basic item"
                       v-for="obj in getReportableObjs({account: object.attributed_to, channel: object})"
                       :key="obj.target.type + obj.target.id"
-                      @click.stop.prevent="$store.dispatch('moderation/report', obj.target)">
+                      href=""
+                      class="basic item"
+                      @click.stop.prevent="$store.dispatch('moderation/report', obj.target)"
+                    >
                       <i class="share icon" /> {{ obj.label }}
                     </a>
 
                     <template v-if="isOwner">
-                      <div class="divider"></div>
+                      <div class="divider" />
                       <a
                         class="item"
                         href=""
-                        @click.stop.prevent="showEditModal = true">
+                        @click.stop.prevent="showEditModal = true"
+                      >
                         <translate translate-context="*/*/*/Verb">Edit…</translate>
                       </a>
                       <dangerous-button
-                        :class="['ui', {loading: isLoading}, 'item']"
                         v-if="object"
-                        @confirm="remove()">
-                        <translate translate-context="*/*/*/Verb">Delete…</translate>
-                        <p slot="modal-header"><translate translate-context="Popup/Channel/Title">Delete this Channel?</translate></p>
+                        :class="['ui', {loading: isLoading}, 'item']"
+                        @confirm="remove()"
+                      >
+                        <translate translate-context="*/*/*/Verb">
+                          Delete…
+                        </translate>
+                        <p slot="modal-header">
+                          <translate translate-context="Popup/Channel/Title">
+                            Delete this Channel?
+                          </translate>
+                        </p>
                         <div slot="modal-content">
-                          <p><translate translate-context="Content/Moderation/Paragraph">The channel will be deleted, as well as any related files and data. This action is irreversible.</translate></p>
+                          <p>
+                            <translate translate-context="Content/Moderation/Paragraph">
+                              The channel will be deleted, as well as any related files and data. This action is irreversible.
+                            </translate>
+                          </p>
                         </div>
-                        <p slot="modal-confirm"><translate translate-context="*/*/*/Verb">Delete</translate></p>
+                        <p slot="modal-confirm">
+                          <translate translate-context="*/*/*/Verb">
+                            Delete
+                          </translate>
+                        </p>
                       </dangerous-button>
                     </template>
-                    <template v-if="$store.state.auth.availablePermissions['library']" >
-                      <div class="divider"></div>
-                      <router-link class="basic item" :to="{name: 'manage.channels.detail', params: {id: object.uuid}}">
-                        <i class="wrench icon"></i>
-                        <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
+                    <template v-if="$store.state.auth.availablePermissions['library']">
+                      <div class="divider" />
+                      <router-link
+                        class="basic item"
+                        :to="{name: 'manage.channels.detail', params: {id: object.uuid}}"
+                      >
+                        <i class="wrench icon" />
+                        <translate translate-context="Content/Moderation/Link">
+                          Open in moderation interface
+                        </translate>
                       </router-link>
                     </template>
                   </div>
@@ -134,56 +242,115 @@
               </div>
             </div>
             <h1 class="ui header">
-              <div class="left aligned" :title="object.artist.name">
+              <div
+                class="left aligned"
+                :title="object.artist.name"
+              >
                 {{ object.artist.name }}
-                <div class="ui hidden very small divider"></div>
-                <div class="sub header ellipsis" v-if="object.actor" :title="object.actor.full_username">
+                <div class="ui hidden very small divider" />
+                <div
+                  v-if="object.actor"
+                  class="sub header ellipsis"
+                  :title="object.actor.full_username"
+                >
                   {{ object.actor.full_username }}
                 </div>
-                <div v-else class="sub header ellipsis">
-                  <a :href="object.url || object.rss_url" rel="noopener noreferrer" target="_blank">
-                    <i class="external link icon"></i>
-                    <translate :translate-params="{domain: externalDomain}" translate-context="Content/Channel/Paragraph">Mirrored from %{ domain }</translate>
+                <div
+                  v-else
+                  class="sub header ellipsis"
+                >
+                  <a
+                    :href="object.url || object.rss_url"
+                    rel="noopener noreferrer"
+                    target="_blank"
+                  >
+                    <i class="external link icon" />
+                    <translate
+                      :translate-params="{domain: externalDomain}"
+                      translate-context="Content/Channel/Paragraph"
+                    >Mirrored from %{ domain }</translate>
                   </a>
                 </div>
               </div>
             </h1>
             <div class="header-buttons">
-              <div class="ui buttons" v-if="isOwner">
-                <button class="ui basic labeled icon button" @click.prevent.stop="$store.commit('channels/showUploadModal', {show: true, config: {channel: object}})">
-                  <i class="upload icon"></i>
-                  <translate translate-context="Content/Channels/Button.Label/Verb">Upload</translate>
+              <div
+                v-if="isOwner"
+                class="ui buttons"
+              >
+                <button
+                  class="ui basic labeled icon button"
+                  @click.prevent.stop="$store.commit('channels/showUploadModal', {show: true, config: {channel: object}})"
+                >
+                  <i class="upload icon" />
+                  <translate translate-context="Content/Channels/Button.Label/Verb">
+                    Upload
+                  </translate>
                 </button>
               </div>
               <div class="ui buttons">
-                <play-button :is-playable="isPlayable" class="vibrant" :artist="object.artist">
-                  <translate translate-context="Content/Channels/Button.Label/Verb">Play</translate>
+                <play-button
+                  :is-playable="isPlayable"
+                  class="vibrant"
+                  :artist="object.artist"
+                >
+                  <translate translate-context="Content/Channels/Button.Label/Verb">
+                    Play
+                  </translate>
                 </play-button>
               </div>
               <div class="ui buttons">
-                <subscribe-button @subscribed="object.subscriptions_count += 1" @unsubscribed="object.subscriptions_count -= 1" :channel="object"></subscribe-button>
+                <subscribe-button
+                  :channel="object"
+                  @subscribed="object.subscriptions_count += 1"
+                  @unsubscribed="object.subscriptions_count -= 1"
+                />
               </div>
 
-              <modal :show.sync="showEmbedModal" v-if="totalTracks > 0">
+              <modal
+                v-if="totalTracks > 0"
+                :show.sync="showEmbedModal"
+              >
                 <h4 class="header">
-                  <translate translate-context="Popup/Artist/Title/Verb">Embed this artist work on your website</translate>
+                  <translate translate-context="Popup/Artist/Title/Verb">
+                    Embed this artist work on your website
+                  </translate>
                 </h4>
                 <div class="scrolling content">
                   <div class="description">
-                    <embed-wizard type="artist" :id="object.artist.id" />
+                    <embed-wizard
+                      :id="object.artist.id"
+                      type="artist"
+                    />
                   </div>
                 </div>
                 <div class="actions">
                   <button class="ui basic deny button">
-                    <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+                    <translate translate-context="*/*/Button.Label/Verb">
+                      Cancel
+                    </translate>
                   </button>
                 </div>
               </modal>
-              <modal :show.sync="showEditModal" v-if="isOwner">
+              <modal
+                v-if="isOwner"
+                :show.sync="showEditModal"
+              >
                 <h4 class="header">
-                  <translate v-if="object.artist.content_category === 'podcast'" key="1" translate-context="Content/Channel/*">Podcast channel</translate>
-                  <translate v-else key="2" translate-context="Content/Channel/*">Artist channel</translate>
-
+                  <translate
+                    v-if="object.artist.content_category === 'podcast'"
+                    key="1"
+                    translate-context="Content/Channel/*"
+                  >
+                    Podcast channel
+                  </translate>
+                  <translate
+                    v-else
+                    key="2"
+                    translate-context="Content/Channel/*"
+                  >
+                    Artist channel
+                  </translate>
                 </h4>
                 <div class="scrolling content">
                   <channel-form
@@ -191,39 +358,75 @@
                     :object="object"
                     @loading="edit.isLoading = $event"
                     @submittable="edit.submittable = $event"
-                    @updated="fetchData"></channel-form>
-                    <div class="ui hidden divider"></div>
+                    @updated="fetchData"
+                  />
+                  <div class="ui hidden divider" />
                 </div>
                 <div class="actions">
                   <button class="ui left floated basic deny button">
-                    <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+                    <translate translate-context="*/*/Button.Label/Verb">
+                      Cancel
+                    </translate>
                   </button>
-                  <button @click.stop="$refs.editForm.submit" :class="['ui', 'primary', 'confirm', {loading: edit.isLoading}, 'button']" :disabled="!edit.submittable">
-                    <translate translate-context="*/Channels/Button.Label">Update channel</translate>
+                  <button
+                    :class="['ui', 'primary', 'confirm', {loading: edit.isLoading}, 'button']"
+                    :disabled="!edit.submittable"
+                    @click.stop="$refs.editForm.submit"
+                  >
+                    <translate translate-context="*/Channels/Button.Label">
+                      Update channel
+                    </translate>
                   </button>
                 </div>
               </modal>
             </div>
             <div v-if="$store.getters['ui/layoutVersion'] === 'large'">
               <rendered-description
-                @updated="object = $event"
                 :content="object.artist.description"
                 :update-url="`channels/${object.uuid}/`"
-                :can-update="false"></rendered-description>
+                :can-update="false"
+                @updated="object = $event"
+              />
             </div>
           </div>
           <div class="nine wide column">
             <div class="ui secondary pointing center aligned menu">
-              <router-link class="item" :exact="true" :to="{name: 'channels.detail', params: {id: id}}">
-                <translate translate-context="Content/Channels/Link">Overview</translate>
+              <router-link
+                class="item"
+                :exact="true"
+                :to="{name: 'channels.detail', params: {id: id}}"
+              >
+                <translate translate-context="Content/Channels/Link">
+                  Overview
+                </translate>
               </router-link>
-              <router-link class="item" :exact="true" :to="{name: 'channels.detail.episodes', params: {id: id}}">
-                <translate key="1" v-if="isPodcast" translate-context="Content/Channels/*">All Episodes</translate>
-                <translate key="2" v-else translate-context="*/*/*">Tracks</translate>
+              <router-link
+                class="item"
+                :exact="true"
+                :to="{name: 'channels.detail.episodes', params: {id: id}}"
+              >
+                <translate
+                  v-if="isPodcast"
+                  key="1"
+                  translate-context="Content/Channels/*"
+                >
+                  All Episodes
+                </translate>
+                <translate
+                  v-else
+                  key="2"
+                  translate-context="*/*/*"
+                >
+                  Tracks
+                </translate>
               </router-link>
             </div>
-            <div class="ui hidden divider"></div>
-            <router-view v-if="object" :object="object" @tracks-loaded="totalTracks = $event"></router-view>
+            <div class="ui hidden divider" />
+            <router-view
+              v-if="object"
+              :object="object"
+              @tracks-loaded="totalTracks = $event"
+            />
           </div>
         </div>
       </section>
@@ -232,32 +435,32 @@
 </template>
 
 <script>
-import axios from "axios"
-import PlayButton from "@/components/audio/PlayButton"
-import ChannelEntries from "@/components/audio/ChannelEntries"
-import ChannelSeries from "@/components/audio/ChannelSeries"
-import EmbedWizard from "@/components/audio/EmbedWizard"
+import axios from 'axios'
+import PlayButton from '@/components/audio/PlayButton'
+import EmbedWizard from '@/components/audio/EmbedWizard'
 import Modal from '@/components/semantic/Modal'
-import TagsList from "@/components/tags/List"
+import TagsList from '@/components/tags/List'
 import ReportMixin from '@/components/mixins/Report'
 
 import SubscribeButton from '@/components/channels/SubscribeButton'
-import ChannelForm from "@/components/audio/ChannelForm"
+import ChannelForm from '@/components/audio/ChannelForm'
 
 export default {
-  mixins: [ReportMixin],
-  props: ["id"],
   components: {
     PlayButton,
     EmbedWizard,
     Modal,
     TagsList,
-    ChannelEntries,
-    ChannelSeries,
     SubscribeButton,
-    ChannelForm,
+    ChannelForm
+  },
+  mixins: [ReportMixin],
+  beforeRouteUpdate (to, from, next) {
+    to.meta.preserveScrollPosition = true
+    next()
   },
-  data() {
+  props: { id: { type: Number, required: true } },
+  data () {
     return {
       isLoading: true,
       object: null,
@@ -268,39 +471,67 @@ export default {
       showSubscribeModal: false,
       edit: {
         submittable: false,
-        loading: false,
+        loading: false
       }
     }
   },
-  beforeRouteUpdate (to, from, next) {
-    to.meta.preserveScrollPosition = true
-    next()
+  computed: {
+    externalDomain () {
+      const parser = document.createElement('a')
+      parser.href = this.object.url || this.object.rss_url
+      return parser.hostname
+    },
+
+    isOwner () {
+      return this.$store.state.auth.authenticated && this.object.attributed_to.full_username === this.$store.state.auth.fullUsername
+    },
+    isPodcast () {
+      return this.object.artist.content_category === 'podcast'
+    },
+    labels () {
+      return {
+        title: this.$pgettext('*/*/*', 'Channel')
+      }
+    },
+    contentFilter () {
+      return this.$store.getters['moderation/artistFilters']().filter((e) => {
+        return e.target.id === this.object.artist.id
+      })[0]
+    },
+    isPlayable () {
+      return this.totalTracks > 0
+    }
+  },
+  watch: {
+    id () {
+      this.fetchData()
+    }
   },
-  async created() {
+  async created () {
     await this.fetchData()
-    let authenticated = this.$store.state.auth.authenticated
-    if (!authenticated && this.$store.getters['instance/domain'] != this.object.actor.domain) {
-      this.$router.push({name: 'login', query: {next: this.$route.fullPath}})
+    const authenticated = this.$store.state.auth.authenticated
+    if (!authenticated && this.$store.getters['instance/domain'] !== this.object.actor.domain) {
+      this.$router.push({ name: 'login', query: { next: this.$route.fullPath } })
     }
   },
   methods: {
-    async fetchData() {
-      var self = this
+    async fetchData () {
+      const self = this
       this.showEditModal = false
       this.edit.isLoading = false
       this.isLoading = true
-      let channelPromise = axios.get(`channels/${this.id}`, {params: {refresh: 'true'}}).then(response => {
+      const channelPromise = axios.get(`channels/${this.id}`, { params: { refresh: 'true' } }).then(response => {
         self.object = response.data
-        if ((self.id == response.data.uuid) && response.data.actor) {
+        if ((self.id === response.data.uuid) && response.data.actor) {
           // replace with the pretty channel url if possible
-          let actor = response.data.actor
+          const actor = response.data.actor
           if (actor.is_local) {
-            self.$router.replace({name: 'channels.detail', params: {id: actor.preferred_username}})
+            self.$router.replace({ name: 'channels.detail', params: { id: actor.preferred_username } })
           } else {
-            self.$router.replace({name: 'channels.detail', params: {id: actor.full_username}})
+            self.$router.replace({ name: 'channels.detail', params: { id: actor.full_username } })
           }
         }
-        let tracksPromise = axios.get("tracks", {params: {channel: response.data.uuid, page_size: 1, playable: true, include_channels: true}}).then(response => {
+        axios.get('tracks', { params: { channel: response.data.uuid, page_size: 1, playable: true, include_channels: true } }).then(response => {
           self.totalTracks = response.data.count
           self.isLoading = false
         })
@@ -308,50 +539,17 @@ export default {
       await channelPromise
     },
     remove () {
-      let self = this
+      const self = this
       self.isLoading = true
       axios.delete(`channels/${this.object.uuid}`).then((response) => {
         self.isLoading = false
         self.$emit('deleted')
-        self.$router.push({name: 'profile.overview', params: {username: self.$store.state.auth.username}})
+        self.$router.push({ name: 'profile.overview', params: { username: self.$store.state.auth.username } })
       }, error => {
         self.isLoading = false
         self.errors = error.backendErrors
       })
     }
-  },
-  computed: {
-    externalDomain () {
-      let parser = document.createElement('a')
-      parser.href = this.object.url || this.object.rss_url
-      return parser.hostname
-    },
-
-    isOwner () {
-      return this.$store.state.auth.authenticated && this.object.attributed_to.full_username === this.$store.state.auth.fullUsername
-    },
-    isPodcast () {
-      return this.object.artist.content_category === 'podcast'
-    },
-    labels () {
-      return {
-        title: this.$pgettext('*/*/*', 'Channel')
-      }
-    },
-    contentFilter () {
-      let self = this
-      return this.$store.getters['moderation/artistFilters']().filter((e) => {
-        return e.target.id === this.object.artist.id
-      })[0]
-    },
-    isPlayable () {
-      return this.totalTracks > 0
-    },
-  },
-  watch: {
-    id() {
-      this.fetchData()
-    }
   }
 }
 </script>
diff --git a/front/src/views/channels/DetailEpisodes.vue b/front/src/views/channels/DetailEpisodes.vue
index 09de17f90..6d01f8736 100644
--- a/front/src/views/channels/DetailEpisodes.vue
+++ b/front/src/views/channels/DetailEpisodes.vue
@@ -1,18 +1,21 @@
 <template>
   <section>
-    <channel-entries :default-cover="object.artist.cover" :is-podcast="object.artist.content_category === 'podcast'" :limit="25" :filters="{channel: object.uuid, ordering: 'creation_date'}">
-    </channel-entries>
+    <channel-entries
+      :default-cover="object.artist.cover"
+      :is-podcast="object.artist.content_category === 'podcast'"
+      :limit="25"
+      :filters="{channel: object.uuid, ordering: 'creation_date'}"
+    />
   </section>
 </template>
 
 <script>
-import ChannelEntries from "@/components/audio/ChannelEntries"
-
+import ChannelEntries from '@/components/audio/ChannelEntries'
 
 export default {
-  props: ['object'],
   components: {
-    ChannelEntries,
+    ChannelEntries
   },
+  props: { object: { type: Object, required: true } }
 }
 </script>
diff --git a/front/src/views/channels/DetailOverview.vue b/front/src/views/channels/DetailOverview.vue
index 54c53a9b4..cca63ded1 100644
--- a/front/src/views/channels/DetailOverview.vue
+++ b/front/src/views/channels/DetailOverview.vue
@@ -1,79 +1,141 @@
 <template>
   <section>
-    <div class="ui info message" v-if="pendingUploads.length > 0">
+    <div
+      v-if="pendingUploads.length > 0"
+      class="ui info message"
+    >
       <template v-if="isSuccessfull">
-        <i role="button" class="close icon" @click="pendingUploads = []"></i>
+        <i
+          role="button"
+          class="close icon"
+          @click="pendingUploads = []"
+        />
         <h3 class="ui header">
-          <translate translate-context="Content/Channel/Header">Uploads published successfully</translate>
+          <translate translate-context="Content/Channel/Header">
+            Uploads published successfully
+          </translate>
         </h3>
         <p>
-          <translate translate-context="Content/Channel/Paragraph">Processed uploads:</translate> {{ processedUploads.length }}/{{ pendingUploads.length }}
+          <translate translate-context="Content/Channel/Paragraph">
+            Processed uploads:
+          </translate> {{ processedUploads.length }}/{{ pendingUploads.length }}
         </p>
       </template>
       <template v-else-if="isOver">
         <h3 class="ui header">
-          <translate translate-context="Content/Channel/Header">Some uploads couldn't be published</translate>
+          <translate translate-context="Content/Channel/Header">
+            Some uploads couldn't be published
+          </translate>
         </h3>
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
         <router-link
+          v-if="skippedUploads.length > 0"
           class="ui basic button"
           :to="{name: 'content.libraries.files', query: {q: 'status:skipped'}}"
-          v-if="skippedUploads.length > 0">
-          <translate translate-context="Content/Channel/Button">View skipped uploads</translate>
+        >
+          <translate translate-context="Content/Channel/Button">
+            View skipped uploads
+          </translate>
         </router-link>
         <router-link
+          v-if="erroredUploads.length > 0"
           class="ui basic button"
           :to="{name: 'content.libraries.files', query: {q: 'status:errored'}}"
-          v-if="erroredUploads.length > 0">
-          <translate translate-context="Content/Channel/Button">View errored uploads</translate>
+        >
+          <translate translate-context="Content/Channel/Button">
+            View errored uploads
+          </translate>
         </router-link>
       </template>
       <template v-else>
-        <div class="ui inline right floated active loader"></div>
+        <div class="ui inline right floated active loader" />
         <h3 class="ui header">
-          <translate translate-context="Content/Channel/Header">Uploads are being processed</translate>
+          <translate translate-context="Content/Channel/Header">
+            Uploads are being processed
+          </translate>
         </h3>
         <p>
-          <translate translate-context="Content/Channel/Paragraph">Your uploads are being processed by Funkwhale and will be live very soon.</translate>
+          <translate translate-context="Content/Channel/Paragraph">
+            Your uploads are being processed by Funkwhale and will be live very soon.
+          </translate>
         </p>
         <p>
-          <translate translate-context="Content/Channel/Paragraph">Processed uploads:</translate> {{ processedUploads.length }}/{{ pendingUploads.length }}
+          <translate translate-context="Content/Channel/Paragraph">
+            Processed uploads:
+          </translate> {{ processedUploads.length }}/{{ pendingUploads.length }}
         </p>
-
       </template>
     </div>
     <div v-if="$store.getters['ui/layoutVersion'] === 'small'">
       <rendered-description
         :content="object.artist.description"
         :update-url="`channels/${object.uuid}/`"
-        :can-update="false"></rendered-description>
-        <div class="ui hidden divider"></div>
+        :can-update="false"
+      />
+      <div class="ui hidden divider" />
     </div>
-    <channel-entries :is-podcast="isPodcast" :key="String(episodesKey) + 'entries'" :default-cover='object.artist.cover' :limit='25' :filters="{channel: object.uuid, ordering: '-creation_date', page_size: '25'}">
+    <channel-entries
+      :key="String(episodesKey) + 'entries'"
+      :is-podcast="isPodcast"
+      :default-cover="object.artist.cover"
+      :limit="25"
+      :filters="{channel: object.uuid, ordering: '-creation_date', page_size: '25'}"
+    >
       <h2 class="ui header">
-        <translate key="1" v-if="isPodcast" translate-context="Content/Channel/Paragraph">Latest episodes</translate>
-        <translate key="2" v-else translate-context="Content/Channel/Paragraph">Latest tracks</translate>
+        <translate
+          v-if="isPodcast"
+          key="1"
+          translate-context="Content/Channel/Paragraph"
+        >
+          Latest episodes
+        </translate>
+        <translate
+          v-else
+          key="2"
+          translate-context="Content/Channel/Paragraph"
+        >
+          Latest tracks
+        </translate>
       </h2>
     </channel-entries>
-    <div class="ui hidden divider"></div>
-    <channel-series :key="String(seriesKey) + 'series'" :filters="seriesFilters" :is-podcast="isPodcast">
+    <div class="ui hidden divider" />
+    <channel-series
+      :key="String(seriesKey) + 'series'"
+      :filters="seriesFilters"
+      :is-podcast="isPodcast"
+    >
       <h2 class="ui with-actions header">
-
-        <translate key="1" v-if="isPodcast" translate-context="Content/Channel/Paragraph">Series</translate>
-        <translate key="2" v-else translate-context="*/*/*">Albums</translate>
-        <div class="actions" v-if="isOwner">
+        <translate
+          v-if="isPodcast"
+          key="1"
+          translate-context="Content/Channel/Paragraph"
+        >
+          Series
+        </translate>
+        <translate
+          v-else
+          key="2"
+          translate-context="*/*/*"
+        >
+          Albums
+        </translate>
+        <div
+          v-if="isOwner"
+          class="actions"
+        >
           <a @click.stop.prevent="$refs.albumModal.show = true">
-            <i class="plus icon"></i>
+            <i class="plus icon" />
             <translate translate-context="Content/Profile/Button">Add new</translate>
           </a>
         </div>
       </h2>
     </channel-series>
     <album-modal
-      ref="albumModal"
       v-if="isOwner"
+      ref="albumModal"
       :channel="object"
-      @created="$refs.albumModal.show = false; seriesKey = new Date()"></album-modal>
+      @created="$refs.albumModal.show = false; seriesKey = new Date()"
+    />
   </section>
 </template>
 
@@ -81,41 +143,24 @@
 import axios from 'axios'
 import qs from 'qs'
 
-import ChannelEntries from "@/components/audio/ChannelEntries"
-import ChannelSeries from "@/components/audio/ChannelSeries"
-import AlbumModal from "@/components/channels/AlbumModal"
-
+import ChannelEntries from '@/components/audio/ChannelEntries'
+import ChannelSeries from '@/components/audio/ChannelSeries'
+import AlbumModal from '@/components/channels/AlbumModal'
 
 export default {
-  props: ['object'],
   components: {
     ChannelEntries,
     ChannelSeries,
-    AlbumModal,
+    AlbumModal
   },
+  props: { object: { type: Object, required: true } },
   data () {
     return {
       seriesKey: new Date(),
       episodesKey: new Date(),
-      pendingUploads: [],
-    }
-  },
-  async created () {
-    if (this.isOwner) {
-      await this.fetchPendingUploads()
-      this.$store.commit("ui/addWebsocketEventHandler", {
-        eventName: "import.status_updated",
-        id: "fileUploadChannel",
-        handler: this.handleImportEvent
-      });
+      pendingUploads: []
     }
   },
-  destroyed() {
-    this.$store.commit("ui/removeWebsocketEventHandler", {
-      eventName: "import.status_updated",
-      id: "fileUploadChannel"
-    });
-  },
   computed: {
     isPodcast () {
       return this.object.artist.content_category === 'podcast'
@@ -124,7 +169,7 @@ export default {
       return this.$store.state.auth.authenticated && this.object.attributed_to.full_username === this.$store.state.auth.fullUsername
     },
     seriesFilters () {
-      let filters = {artist: this.object.artist.id, ordering: '-creation_date'}
+      const filters = { artist: this.object.artist.id, ordering: '-creation_date' }
       if (!this.isOwner) {
         filters.playable = 'true'
       }
@@ -132,26 +177,26 @@ export default {
     },
     processedUploads () {
       return this.pendingUploads.filter((u) => {
-        return u.import_status != "pending"
+        return u.import_status !== 'pending'
       })
     },
     erroredUploads () {
       return this.pendingUploads.filter((u) => {
-        return u.import_status === "errored"
+        return u.import_status === 'errored'
       })
     },
     skippedUploads () {
       return this.pendingUploads.filter((u) => {
-        return u.import_status === "skipped"
+        return u.import_status === 'skipped'
       })
     },
     finishedUploads () {
       return this.pendingUploads.filter((u) => {
-        return u.import_status === "finished"
+        return u.import_status === 'finished'
       })
     },
     pendingUploadsById () {
-      let d = {}
+      const d = {}
       this.pendingUploads.forEach((u) => {
         d[u.uuid] = u
       })
@@ -164,37 +209,51 @@ export default {
       return this.pendingUploads && this.finishedUploads.length === this.pendingUploads.length
     }
   },
+  watch: {
+    '$store.state.channels.latestPublication' (v) {
+      if (v && v.uploads && v.channel.uuid === this.object.uuid) {
+        this.pendingUploads = [...this.pendingUploads, ...v.uploads]
+      }
+    },
+    'isOver' (v) {
+      if (v) {
+        this.seriesKey = new Date()
+        this.episodesKey = new Date()
+      }
+    }
+  },
+  async created () {
+    if (this.isOwner) {
+      await this.fetchPendingUploads()
+      this.$store.commit('ui/addWebsocketEventHandler', {
+        eventName: 'import.status_updated',
+        id: 'fileUploadChannel',
+        handler: this.handleImportEvent
+      })
+    }
+  },
+  destroyed () {
+    this.$store.commit('ui/removeWebsocketEventHandler', {
+      eventName: 'import.status_updated',
+      id: 'fileUploadChannel'
+    })
+  },
   methods: {
-    handleImportEvent(event) {
-      let self = this;
+    handleImportEvent (event) {
       if (!this.pendingUploadsById[event.upload.uuid]) {
-        return;
+        return
       }
       Object.assign(this.pendingUploadsById[event.upload.uuid], event.upload)
     },
     async fetchPendingUploads () {
-      let response = await axios.get('uploads/', {
-        params: {channel: this.object.uuid, import_status: ['pending', 'skipped', 'errored'], include_channels: 'true'},
-        paramsSerializer: function(params) {
+      const response = await axios.get('uploads/', {
+        params: { channel: this.object.uuid, import_status: ['pending', 'skipped', 'errored'], include_channels: 'true' },
+        paramsSerializer: function (params) {
           return qs.stringify(params, { indices: false })
         }
       })
       this.pendingUploads = response.data.results
     }
-  },
-  watch: {
-    "$store.state.channels.latestPublication" (v) {
-      if (v && v.uploads && v.channel.uuid === this.object.uuid) {
-        let test
-        this.pendingUploads = [...this.pendingUploads, ...v.uploads]
-      }
-    },
-    "isOver" (v) {
-      if (v) {
-        this.seriesKey = new Date()
-        this.episodesKey = new Date()
-      }
-    }
   }
 }
 </script>
diff --git a/front/src/views/channels/SubscriptionsList.vue b/front/src/views/channels/SubscriptionsList.vue
index 6ac12fd9d..840c252ae 100644
--- a/front/src/views/channels/SubscriptionsList.vue
+++ b/front/src/views/channels/SubscriptionsList.vue
@@ -1,65 +1,89 @@
 <template>
-  <main class="main pusher" v-title="labels.title">
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
     <section class="ui head vertical stripe segment container">
       <h1 class="ui with-actions header">
         {{ labels.title }}
         <div class="actions">
           <a @click.stop.prevent="showSubscribeModal = true">
-            <i class="plus icon"></i>
+            <i class="plus icon" />
             <translate translate-context="Content/Profile/Button">Add new</translate>
           </a>
         </div>
       </h1>
-      <modal class="tiny" :show.sync="showSubscribeModal" :fullscreen="false">
+      <modal
+        class="tiny"
+        :show.sync="showSubscribeModal"
+        :fullscreen="false"
+      >
         <h2 class="header">
-          <translate translate-context="*/*/*/Noun">Subscription</translate>
+          <translate translate-context="*/*/*/Noun">
+            Subscription
+          </translate>
         </h2>
-        <div class="scrolling content" ref="modalContent">
+        <div
+          ref="modalContent"
+          class="scrolling content"
+        >
           <remote-search-form
             type="both"
             :show-submit="false"
             :standalone="false"
+            :redirect="true"
             @subscribed="showSubscribeModal = false; reloadWidget()"
-            :redirect="true"></remote-search-form>
+          />
         </div>
         <div class="actions">
           <button class="ui basic deny button">
-            <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
+            <translate translate-context="*/*/Button.Label/Verb">
+              Cancel
+            </translate>
           </button>
-          <button form="remote-search" type="submit" class="ui primary button">
-            <i class="bookmark icon"></i>
-            <translate translate-context="*/*/*/Verb">Subscribe</translate>
+          <button
+            form="remote-search"
+            type="submit"
+            class="ui primary button"
+          >
+            <i class="bookmark icon" />
+            <translate translate-context="*/*/*/Verb">
+              Subscribe
+            </translate>
           </button>
         </div>
       </modal>
 
-
-
-      <inline-search-bar v-model="query" @search="reloadWidget" :placeholder="labels.searchPlaceholder"></inline-search-bar>
+      <inline-search-bar
+        v-model="query"
+        :placeholder="labels.searchPlaceholder"
+        @search="reloadWidget"
+      />
       <channels-widget
         :key="widgetKey"
         :limit="50"
         :show-modification-date="true"
-        :filters="{q: query, subscribed: 'true', ordering: '-modification_date'}"></channels-widget>
+        :filters="{q: query, subscribed: 'true', ordering: '-modification_date'}"
+      />
     </section>
   </main>
 </template>
 
 <script>
-import axios from "axios"
+import axios from 'axios'
 import Modal from '@/components/semantic/Modal'
 
-import ChannelsWidget from "@/components/audio/ChannelsWidget"
-import RemoteSearchForm from "@/components/RemoteSearchForm"
+import ChannelsWidget from '@/components/audio/ChannelsWidget'
+import RemoteSearchForm from '@/components/RemoteSearchForm'
 
 export default {
-  props: ["defaultQuery"],
   components: {
     ChannelsWidget,
     RemoteSearchForm,
-    Modal,
+    Modal
   },
-  data() {
+  props: { defaultQuery: { type: String, required: false, default: '' } },
+  data () {
     return {
       query: this.defaultQuery || '',
       channels: [],
@@ -69,25 +93,25 @@ export default {
       previousPage: null,
       nextPage: null,
       widgetKey: String(new Date()),
-      showSubscribeModal: false,
+      showSubscribeModal: false
     }
   },
-  created () {
-    this.fetchData()
-  },
   computed: {
     labels () {
       return {
-        title: this.$pgettext("Content/Subscriptions/Header", "Subscribed Channels"),
-        searchPlaceholder: this.$pgettext("Content/Subscriptions/Form.Placeholder", "Filter by name…"),
+        title: this.$pgettext('Content/Subscriptions/Header', 'Subscribed Channels'),
+        searchPlaceholder: this.$pgettext('Content/Subscriptions/Form.Placeholder', 'Filter by name…')
       }
-    },
+    }
+  },
+  created () {
+    this.fetchData()
   },
   methods: {
-    fetchData() {
-      var self = this
+    fetchData () {
+      const self = this
       this.isLoading = true
-      axios.get('channels/', {params: {subscribed: "true", q: this.query}}).then(response => {
+      axios.get('channels/', { params: { subscribed: 'true', q: this.query } }).then(response => {
         self.previousPage = response.data.previous
         self.nextPage = response.data.next
         self.isLoading = false
@@ -98,6 +122,6 @@ export default {
     reloadWidget () {
       this.widgetKey = String(new Date())
     }
-  },
+  }
 }
 </script>
diff --git a/front/src/views/content/Base.vue b/front/src/views/content/Base.vue
index bd593be55..e7c700d3b 100644
--- a/front/src/views/content/Base.vue
+++ b/front/src/views/content/Base.vue
@@ -1,22 +1,39 @@
 <template>
-  <main class="main pusher"  v-title="labels.title">
-    <nav class="ui secondary pointing menu" role="navigation" :aria-label="labels.secondaryMenu">
+  <main
+    v-title="labels.title"
+    class="main pusher"
+  >
+    <nav
+      class="ui secondary pointing menu"
+      role="navigation"
+      :aria-label="labels.secondaryMenu"
+    >
       <router-link
         class="ui item"
-        :to="{name: 'content.libraries.index'}"><translate translate-context="*/*/*/Noun">Libraries</translate></router-link>
+        :to="{name: 'content.libraries.index'}"
+      >
+        <translate translate-context="*/*/*/Noun">
+          Libraries
+        </translate>
+      </router-link>
       <router-link
         class="ui item"
-        :to="{name: 'content.libraries.files'}"><translate translate-context="*/*/*">Tracks</translate></router-link>
+        :to="{name: 'content.libraries.files'}"
+      >
+        <translate translate-context="*/*/*">
+          Tracks
+        </translate>
+      </router-link>
     </nav>
-    <router-view :key="$route.fullPath"></router-view>
+    <router-view :key="$route.fullPath" />
   </main>
 </template>
 <script>
 export default {
   computed: {
-    labels() {
-      let title = this.$pgettext('*/Library/*/Verb', "Add content")
-      let secondaryMenu = this.$pgettext('Menu/*/Hidden text', "Secondary menu")
+    labels () {
+      const title = this.$pgettext('*/Library/*/Verb', 'Add content')
+      const secondaryMenu = this.$pgettext('Menu/*/Hidden text', 'Secondary menu')
       return {
         title,
         secondaryMenu
diff --git a/front/src/views/content/Home.vue b/front/src/views/content/Home.vue
index 533e6d1c8..8532999ee 100644
--- a/front/src/views/content/Home.vue
+++ b/front/src/views/content/Home.vue
@@ -1,60 +1,98 @@
 <template>
-  <section class="ui vertical aligned stripe segment" v-title="labels.title">
+  <section
+    v-title="labels.title"
+    class="ui vertical aligned stripe segment"
+  >
     <div class="ui text container">
       <h1>{{ labels.title }}</h1>
       <p>
-        <strong><translate translate-context="Content/Library/Paragraph" :translate-params="{quota: defaultQuota}">This instance offers up to %{quota} of storage space for every user.</translate></strong>
+        <strong><translate
+          translate-context="Content/Library/Paragraph"
+          :translate-params="{quota: defaultQuota}"
+        >This instance offers up to %{quota} of storage space for every user.</translate></strong>
       </p>
       <div class="ui segment">
         <h2>
-          <i class="feed icon"></i>&nbsp;
-          <translate translate-context="Content/Library/Title/Verb">Publish your work in a channel</translate>
+          <i class="feed icon" />&nbsp;
+          <translate translate-context="Content/Library/Title/Verb">
+            Publish your work in a channel
+          </translate>
         </h2>
         <p>
-          <translate translate-context="Content/Library/Paragraph">If you are a musician or a podcaster, channels are designed for you!</translate>&#32;
-          <translate translate-context="Content/Library/Paragraph">Share your work publicly and get subscribers on Funkwhale, the Fediverse or any podcasting application.</translate>
+          <translate translate-context="Content/Library/Paragraph">
+            If you are a musician or a podcaster, channels are designed for you!
+          </translate>&#32;
+          <translate translate-context="Content/Library/Paragraph">
+            Share your work publicly and get subscribers on Funkwhale, the Fediverse or any podcasting application.
+          </translate>
         </p>
-        <router-link :to="{name: 'profile.overview', params: {username: $store.state.auth.username}, hash: '#channels'}" class="ui primary button">
-          <translate translate-context="Content/Library/Button.Label/Verb">Get started</translate>
+        <router-link
+          :to="{name: 'profile.overview', params: {username: $store.state.auth.username}, hash: '#channels'}"
+          class="ui primary button"
+        >
+          <translate translate-context="Content/Library/Button.Label/Verb">
+            Get started
+          </translate>
         </router-link>
       </div>
       <div class="ui segment">
         <h2>
-          <i class="cloud icon"></i>&nbsp;
-          <translate translate-context="Content/Library/Title/Verb">Upload third-party content in a library</translate>
+          <i class="cloud icon" />&nbsp;
+          <translate translate-context="Content/Library/Title/Verb">
+            Upload third-party content in a library
+          </translate>
         </h2>
-        <p><translate translate-context="Content/Library/Paragraph">Upload your personal music library to Funkwhale to enjoy it from anywhere and share it with friends and family.</translate></p>
-        <router-link :to="{name: 'content.libraries.index'}" class="ui primary button">
-          <translate translate-context="Content/Library/Button.Label/Verb">Get started</translate>
+        <p>
+          <translate translate-context="Content/Library/Paragraph">
+            Upload your personal music library to Funkwhale to enjoy it from anywhere and share it with friends and family.
+          </translate>
+        </p>
+        <router-link
+          :to="{name: 'content.libraries.index'}"
+          class="ui primary button"
+        >
+          <translate translate-context="Content/Library/Button.Label/Verb">
+            Get started
+          </translate>
         </router-link>
       </div>
       <div class="ui segment">
         <h2>
-          <i class="download icon"></i>&nbsp;
-          <translate translate-context="Content/Library/Title/Verb">Follow remote libraries</translate>
+          <i class="download icon" />&nbsp;
+          <translate translate-context="Content/Library/Title/Verb">
+            Follow remote libraries
+          </translate>
         </h2>
-        <p><translate translate-context="Content/Library/Paragraph">Follow libraries from other users to get access to new music. Public libraries can be followed immediately, while following a private library requires approval from its owner.</translate></p>
-        <router-link :to="{name: 'content.remote.index'}" class="ui primary button">
-          <translate translate-context="Content/Library/Button.Label/Verb">Get started</translate>
+        <p>
+          <translate translate-context="Content/Library/Paragraph">
+            Follow libraries from other users to get access to new music. Public libraries can be followed immediately, while following a private library requires approval from its owner.
+          </translate>
+        </p>
+        <router-link
+          :to="{name: 'content.remote.index'}"
+          class="ui primary button"
+        >
+          <translate translate-context="Content/Library/Button.Label/Verb">
+            Get started
+          </translate>
         </router-link>
       </div>
-
     </div>
   </section>
 </template>
 
 <script>
-import { humanSize } from "@/filters"
+import { humanSize } from '@/filters'
 
 export default {
   computed: {
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext('Content/Library/Title/Verb', "Add and manage content")
+        title: this.$pgettext('Content/Library/Title/Verb', 'Add and manage content')
       }
     },
-    defaultQuota() {
-      let quota =
+    defaultQuota () {
+      const quota =
         this.$store.state.instance.settings.users.upload_quota.value *
         1000 *
         1000
diff --git a/front/src/views/content/libraries/Card.vue b/front/src/views/content/libraries/Card.vue
index ca8e53e25..d7cb065d6 100644
--- a/front/src/views/content/libraries/Card.vue
+++ b/front/src/views/content/libraries/Card.vue
@@ -6,20 +6,23 @@
         <span
           v-if="library.privacy_level === 'me'"
           class="right floated"
-          :data-tooltip="privacy_tooltips('me')">
-          <i class="small lock icon"></i>
+          :data-tooltip="privacy_tooltips('me')"
+        >
+          <i class="small lock icon" />
         </span>
         <span
           v-else-if="library.privacy_level === 'instance'"
           class="right floated"
-          :data-tooltip="privacy_tooltips('instance')">
-          <i class="small circle outline icon"></i>
+          :data-tooltip="privacy_tooltips('instance')"
+        >
+          <i class="small circle outline icon" />
         </span>
         <span
           v-else-if="library.privacy_level === 'everyone'"
           class="right floated"
-          :data-tooltip="privacy_tooltips('everyone')">
-          <i class="small globe icon"></i>
+          :data-tooltip="privacy_tooltips('everyone')"
+        >
+          <i class="small globe icon" />
         </span>
       </h4>
       <div class="meta">
@@ -30,23 +33,45 @@
       </div>
       <div class="description">
         {{ library.description }}
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
       </div>
       <div class="content">
-        <span v-if="library.size" class="right floated" :data-tooltip="size_label">
-          <i class="database icon"></i>
+        <span
+          v-if="library.size"
+          class="right floated"
+          :data-tooltip="size_label"
+        >
+          <i class="database icon" />
           {{ library.size | humanSize }}
         </span>
-        <i class="music icon"></i>
-        <translate translate-context="*/*/*" :translate-params="{count: library.uploads_count}" :translate-n="library.uploads_count" translate-plural="%{ count } tracks">%{ count } track</translate>
+        <i class="music icon" />
+        <translate
+          translate-context="*/*/*"
+          :translate-params="{count: library.uploads_count}"
+          :translate-n="library.uploads_count"
+          translate-plural="%{ count } tracks"
+        >
+          %{ count } track
+        </translate>
       </div>
     </div>
     <div class="ui bottom basic attached buttons">
-      <router-link :to="{name: 'library.detail.upload', params: {id: library.uuid}}" class="ui button">
-        <translate translate-context="Content/Library/Card.Button.Label/Verb">Upload</translate>
+      <router-link
+        :to="{name: 'library.detail.upload', params: {id: library.uuid}}"
+        class="ui button"
+      >
+        <translate translate-context="Content/Library/Card.Button.Label/Verb">
+          Upload
+        </translate>
       </router-link>
-      <router-link :to="{name: 'library.detail', params: {id: library.uuid}}" exact class="ui button">
-        <translate translate-context="Content/Library/Card.Button.Label/Noun">Library Details</translate>
+      <router-link
+        :to="{name: 'library.detail', params: {id: library.uuid}}"
+        exact
+        class="ui button"
+      >
+        <translate translate-context="Content/Library/Card.Button.Label/Noun">
+          Library Details
+        </translate>
       </router-link>
     </div>
   </div>
@@ -57,16 +82,16 @@ import TranslationsMixin from '@/components/mixins/Translations'
 
 export default {
   mixins: [TranslationsMixin],
-  props: ['library'],
-  methods: {
-    privacy_tooltips (level) {
-      return 'Visibility: ' + this.sharedLabels.fields.privacy_level.choices[level].toLowerCase()
-    },
-  },
+  props: { library: { type: Object, required: true } },
   computed: {
     size_label () {
       return this.$pgettext('Content/Library/Card.Help text', 'Total size of the files in this library')
-    },
+    }
+  },
+  methods: {
+    privacy_tooltips (level) {
+      return 'Visibility: ' + this.sharedLabels.fields.privacy_level.choices[level].toLowerCase()
+    }
   }
 }
 </script>
diff --git a/front/src/views/content/libraries/Files.vue b/front/src/views/content/libraries/Files.vue
index 0184df386..c33824ef4 100644
--- a/front/src/views/content/libraries/Files.vue
+++ b/front/src/views/content/libraries/Files.vue
@@ -1,16 +1,16 @@
 <template>
   <section class="ui vertical aligned stripe segment">
-    <library-files-table :default-query="query"></library-files-table>
+    <library-files-table :default-query="query" />
   </section>
 </template>
 
 <script>
-import LibraryFilesTable from "./FilesTable"
+import LibraryFilesTable from './FilesTable'
 
 export default {
-  props: ["query"],
   components: {
     LibraryFilesTable
-  }
+  },
+  props: { query: { type: String, required: true } }
 }
 </script>
diff --git a/front/src/views/content/libraries/FilesTable.vue b/front/src/views/content/libraries/FilesTable.vue
index 5481f368a..9e794961a 100644
--- a/front/src/views/content/libraries/FilesTable.vue
+++ b/front/src/views/content/libraries/FilesTable.vue
@@ -8,13 +8,13 @@
           </label>
           <form @submit.prevent="search.query = $refs.search.value">
             <input
-              name="search"
+              id="files-search"
               ref="search"
+              name="search"
               type="text"
-              id="files-search"
               :value="search.query"
               :placeholder="labels.searchPlaceholder"
-            />
+            >
           </form>
         </div>
         <div class="field">
@@ -24,26 +24,38 @@
           <select
             id="import-status"
             class="ui dropdown"
-            @change="addSearchToken('status', $event.target.value)"
             :value="getTokenValue('status', '')"
+            @change="addSearchToken('status', $event.target.value)"
           >
             <option value>
-              <translate translate-context="Content/*/Dropdown">All</translate>
+              <translate translate-context="Content/*/Dropdown">
+                All
+              </translate>
             </option>
             <option value="draft">
-              <translate translate-context="Content/Library/*/Short">Draft</translate>
+              <translate translate-context="Content/Library/*/Short">
+                Draft
+              </translate>
             </option>
             <option value="pending">
-              <translate translate-context="Content/Library/*/Short">Pending</translate>
+              <translate translate-context="Content/Library/*/Short">
+                Pending
+              </translate>
             </option>
             <option value="skipped">
-              <translate translate-context="Content/Library/*">Skipped</translate>
+              <translate translate-context="Content/Library/*">
+                Skipped
+              </translate>
             </option>
             <option value="errored">
-              <translate translate-context="Content/Library/Dropdown">Failed</translate>
+              <translate translate-context="Content/Library/Dropdown">
+                Failed
+              </translate>
             </option>
             <option value="finished">
-              <translate translate-context="Content/Library/*">Finished</translate>
+              <translate translate-context="Content/Library/*">
+                Finished
+              </translate>
             </option>
           </select>
         </div>
@@ -51,44 +63,69 @@
           <label for="ordering-select">
             <translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate>
           </label>
-          <select id="ordering-select" class="ui dropdown" v-model="ordering">
+          <select
+            id="ordering-select"
+            v-model="ordering"
+            class="ui dropdown"
+          >
             <option
-              v-for="option in orderingOptions"
+              v-for="(option, key) in orderingOptions"
+              :key="key"
               :value="option[0]"
-            >{{ sharedLabels.filters[option[1]] }}</option>
+            >
+              {{ sharedLabels.filters[option[1]] }}
+            </option>
           </select>
         </div>
         <div class="field">
           <label for="ordering-direction">
             <translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering direction</translate>
           </label>
-          <select id="ordering-direction" class="ui dropdown" v-model="orderingDirection">
+          <select
+            id="ordering-direction"
+            v-model="orderingDirection"
+            class="ui dropdown"
+          >
             <option value="+">
-              <translate translate-context="Content/Search/Dropdown">Ascending</translate>
+              <translate translate-context="Content/Search/Dropdown">
+                Ascending
+              </translate>
             </option>
             <option value="-">
-              <translate translate-context="Content/Search/Dropdown">Descending</translate>
+              <translate translate-context="Content/Search/Dropdown">
+                Descending
+              </translate>
             </option>
           </select>
         </div>
       </div>
     </div>
-    <import-status-modal :upload="detailedUpload" :show.sync="showUploadDetailModal" />
+    <import-status-modal
+      :upload="detailedUpload"
+      :show.sync="showUploadDetailModal"
+    />
     <div class="dimmable">
-      <div v-if="isLoading" class="ui active inverted dimmer">
-        <div class="ui loader"></div>
+      <div
+        v-if="isLoading"
+        class="ui active inverted dimmer"
+      >
+        <div class="ui loader" />
       </div>
-      <div v-else-if="!result && result.results.length === 0 && !needsRefresh" class="ui placeholder segment">
+      <div
+        v-else-if="!result && result.results.length === 0 && !needsRefresh"
+        class="ui placeholder segment"
+      >
         <div class="ui icon header">
-          <i class="upload icon"></i>
+          <i class="upload icon" />
           <translate
-          translate-context="Content/Home/Placeholder"
-          >No tracks have been added to this library yet</translate>
+            translate-context="Content/Home/Placeholder"
+          >
+            No tracks have been added to this library yet
+          </translate>
         </div>
       </div>
       <action-table
         v-else
-        @action-launched="fetchData"
         :id-field="'uuid'"
         :objects-data="result"
         :custom-objects="customObjects"
@@ -96,33 +133,51 @@
         :refreshable="true"
         :needs-refresh="needsRefresh"
         :action-url="'uploads/action/'"
-        @refresh="fetchData"
         :filters="actionFilters"
+        @action-launched="fetchData"
+        @refresh="fetchData"
       >
         <template slot="header-cells">
           <th>
-            <translate translate-context="*/*/*/Noun">Title</translate>
+            <translate translate-context="*/*/*/Noun">
+              Title
+            </translate>
           </th>
           <th>
-            <translate translate-context="*/*/*/Noun">Artist</translate>
+            <translate translate-context="*/*/*/Noun">
+              Artist
+            </translate>
           </th>
           <th>
-            <translate translate-context="*/*/*">Album</translate>
+            <translate translate-context="*/*/*">
+              Album
+            </translate>
           </th>
           <th>
-            <translate translate-context="*/*/*/Noun">Upload date</translate>
+            <translate translate-context="*/*/*/Noun">
+              Upload date
+            </translate>
           </th>
           <th>
-            <translate translate-context="Content/*/*/Noun">Import status</translate>
+            <translate translate-context="Content/*/*/Noun">
+              Import status
+            </translate>
           </th>
           <th>
-            <translate translate-context="Content/*/*">Duration</translate>
+            <translate translate-context="Content/*/*">
+              Duration
+            </translate>
           </th>
           <th>
-            <translate translate-context="Content/*/*/Noun">Size</translate>
+            <translate translate-context="Content/*/*/Noun">
+              Size
+            </translate>
           </th>
         </template>
-        <template slot="row-cells" slot-scope="scope">
+        <template
+          slot="row-cells"
+          slot-scope="scope"
+        >
           <template v-if="scope.obj.track">
             <td>
               <router-link :to="{name: 'library.tracks.detail', params: {id: scope.obj.track.id }}">
@@ -138,27 +193,29 @@
             </td>
             <td>
               <a
-                href=""
                 v-if="scope.obj.track.album"
+                href=""
                 class="discrete link"
                 @click.prevent="addSearchToken('album', scope.obj.track.album.title)"
               >{{ scope.obj.track.album.title|truncate(20) }}</a>
             </td>
           </template>
           <template v-else>
-            <td :title="scope.obj.source">{{ scope.obj.source | truncate(25) }}</td>
-            <td></td>
-            <td></td>
+            <td :title="scope.obj.source">
+              {{ scope.obj.source | truncate(25) }}
+            </td>
+            <td />
+            <td />
           </template>
           <td>
-            <human-date :date="scope.obj.creation_date"></human-date>
+            <human-date :date="scope.obj.creation_date" />
           </td>
           <td>
-            <a 
+            <a
               href=""
               class="discrete link"
-              @click.prevent="addSearchToken('status', scope.obj.import_status)"
               :title="sharedLabels.fields.import_status.choices[scope.obj.import_status].help"
+              @click.prevent="addSearchToken('status', scope.obj.import_status)"
             >{{ sharedLabels.fields.import_status.choices[scope.obj.import_status].label }}</a>
             <button
               class="ui tiny basic icon button"
@@ -166,16 +223,24 @@
               :aria-label="labels.showStatus"
               @click="detailedUpload = scope.obj; showUploadDetailModal = true"
             >
-              <i class="question circle outline icon"></i>
+              <i class="question circle outline icon" />
             </button>
           </td>
-          <td v-if="scope.obj.duration">{{ scope.obj.duration | duration }}</td>
+          <td v-if="scope.obj.duration">
+            {{ scope.obj.duration | duration }}
+          </td>
           <td v-else>
-            <translate translate-context="*/*/*">N/A</translate>
+            <translate translate-context="*/*/*">
+              N/A
+            </translate>
+          </td>
+          <td v-if="scope.obj.size">
+            {{ scope.obj.size | humanSize }}
           </td>
-          <td v-if="scope.obj.size">{{ scope.obj.size | humanSize }}</td>
           <td v-else>
-            <translate translate-context="*/*/*">N/A</translate>
+            <translate translate-context="*/*/*">
+              N/A
+            </translate>
           </td>
         </template>
       </action-table>
@@ -183,12 +248,12 @@
     <div>
       <pagination
         v-if="result && result.count > paginateBy"
-        @page-changed="page = $event; fetchData()"
         :compact="true"
         :current="page"
         :paginate-by="paginateBy"
         :total="result.count"
-      ></pagination>
+        @page-changed="page = $event; fetchData()"
+      />
 
       <span v-if="result && result.results.length > 0">
         <translate
@@ -201,37 +266,37 @@
 </template>
 
 <script>
-import axios from "axios";
-import _ from "@/lodash";
-import time from "@/utils/time";
-import { normalizeQuery, parseTokens } from "@/search";
+import axios from 'axios'
+import _ from '@/lodash'
+import time from '@/utils/time'
+import { normalizeQuery, parseTokens } from '@/search'
 
-import Pagination from "@/components/Pagination";
-import ActionTable from "@/components/common/ActionTable";
-import OrderingMixin from "@/components/mixins/Ordering";
-import TranslationsMixin from "@/components/mixins/Translations";
-import SmartSearchMixin from "@/components/mixins/SmartSearch";
-import ImportStatusModal from "@/components/library/ImportStatusModal";
+import Pagination from '@/components/Pagination'
+import ActionTable from '@/components/common/ActionTable'
+import OrderingMixin from '@/components/mixins/Ordering'
+import TranslationsMixin from '@/components/mixins/Translations'
+import SmartSearchMixin from '@/components/mixins/SmartSearch'
+import ImportStatusModal from '@/components/library/ImportStatusModal'
 
 export default {
+  components: {
+    Pagination,
+    ActionTable,
+    ImportStatusModal
+  },
   mixins: [OrderingMixin, TranslationsMixin, SmartSearchMixin],
   props: {
-    filters: { type: Object, required: false },
+    filters: { type: Object, required: false, default: function () { return {} } },
     needsRefresh: { type: Boolean, required: false, default: false },
     customObjects: {
       type: Array,
       required: false,
       default: () => {
-        return [];
+        return []
       }
     }
   },
-  components: {
-    Pagination,
-    ActionTable,
-    ImportStatusModal
-  },
-  data() {
+  data () {
     return {
       time,
       detailedUpload: null,
@@ -244,109 +309,109 @@ export default {
         tokens: parseTokens(normalizeQuery(this.defaultQuery))
       },
       orderingOptions: [
-        ["creation_date", "creation_date"],
-        ["title", "track_title"],
-        ["size", "size"],
-        ["duration", "duration"],
-        ["bitrate", "bitrate"],
-        ["album_title", "album_title"],
-        ["artist_name", "artist_name"]
+        ['creation_date', 'creation_date'],
+        ['title', 'track_title'],
+        ['size', 'size'],
+        ['duration', 'duration'],
+        ['bitrate', 'bitrate'],
+        ['album_title', 'album_title'],
+        ['artist_name', 'artist_name']
       ]
-    };
-  },
-  created() {
-    this.fetchData();
-  },
-  methods: {
-    fetchData() {
-      this.$emit("fetch-start");
-      let params = _.merge(
-        {
-          page: this.page,
-          page_size: this.paginateBy,
-          ordering: this.getOrderingAsString(),
-          q: this.search.query,
-          include_channels: 'true',
-        },
-        this.filters || {}
-      );
-      let self = this;
-      self.isLoading = true;
-      self.checked = [];
-      axios.get("/uploads/", { params: params }).then(
-        response => {
-          self.result = response.data;
-          self.isLoading = false;
-        },
-        error => {
-          self.isLoading = false;
-          self.errors = error.backendErrors;
-        }
-      );
-    },
+    }
   },
   computed: {
-    labels() {
+    labels () {
       return {
         searchPlaceholder: this.$pgettext(
-          "Content/Library/Input.Placeholder",
-          "Search by title, artist, album…"
+          'Content/Library/Input.Placeholder',
+          'Search by title, artist, album…'
         ),
         showStatus: this.$pgettext('Content/Library/Button.Label/Verb', 'Show information about the upload status for this track')
-      };
+      }
     },
-    actionFilters() {
-      var currentFilters = {
+    actionFilters () {
+      const currentFilters = {
         q: this.search.query,
-        include_channels: 'true',
-      };
+        include_channels: 'true'
+      }
       if (this.filters) {
-        return _.merge(currentFilters, this.filters);
+        return _.merge(currentFilters, this.filters)
       } else {
-        return currentFilters;
+        return currentFilters
       }
     },
-    actions() {
-      let deleteMsg = this.$pgettext("*/*/*/Verb", "Delete");
-      let relaunchMsg = this.$pgettext(
-        "Content/Library/Dropdown/Verb",
-        "Restart import"
-      );
+    actions () {
+      const deleteMsg = this.$pgettext('*/*/*/Verb', 'Delete')
+      const relaunchMsg = this.$pgettext(
+        'Content/Library/Dropdown/Verb',
+        'Restart import'
+      )
       return [
         {
-          name: "delete",
+          name: 'delete',
           label: deleteMsg,
           isDangerous: true,
           allowAll: true
         },
         {
-          name: "relaunch_import",
+          name: 'relaunch_import',
           label: relaunchMsg,
           isDangerous: true,
           allowAll: true,
           filterCheckable: f => {
-            return f.import_status != "finished";
+            return f.import_status !== 'finished'
           }
         }
-      ];
+      ]
     }
   },
   watch: {
-    orderingDirection: function() {
-      this.page = 1;
-      this.fetchData();
+    orderingDirection: function () {
+      this.page = 1
+      this.fetchData()
     },
-    page: function() {
-      this.fetchData();
+    page: function () {
+      this.fetchData()
     },
-    ordering: function() {
-      this.page = 1;
-      this.fetchData();
+    ordering: function () {
+      this.page = 1
+      this.fetchData()
     },
-    search(newValue) {
-      this.page = 1;
-      this.fetchData();
+    search (newValue) {
+      this.page = 1
+      this.fetchData()
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  methods: {
+    fetchData () {
+      this.$emit('fetch-start')
+      const params = _.merge(
+        {
+          page: this.page,
+          page_size: this.paginateBy,
+          ordering: this.getOrderingAsString(),
+          q: this.search.query,
+          include_channels: 'true'
+        },
+        this.filters || {}
+      )
+      const self = this
+      self.isLoading = true
+      self.checked = []
+      axios.get('/uploads/', { params: params }).then(
+        response => {
+          self.result = response.data
+          self.isLoading = false
+        },
+        error => {
+          self.isLoading = false
+          self.errors = error.backendErrors
+        }
+      )
     }
   }
-};
+}
 </script>
diff --git a/front/src/views/content/libraries/Form.vue b/front/src/views/content/libraries/Form.vue
index dd8e64216..3a81d322a 100644
--- a/front/src/views/content/libraries/Form.vue
+++ b/front/src/views/content/libraries/Form.vue
@@ -1,35 +1,103 @@
 <template>
-  <form class="ui form" @submit.prevent="submit">
-    <p v-if="!library"><translate translate-context="Content/Library/Paragraph">Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family.</translate></p>
-    <div v-if="errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/*/Error message.Title">Error</translate></h4>
+  <form
+    class="ui form"
+    @submit.prevent="submit"
+  >
+    <p v-if="!library">
+      <translate translate-context="Content/Library/Paragraph">
+        Libraries help you organize and share your music collections. You can upload your own music collection to Funkwhale and share it with your friends and family.
+      </translate>
+    </p>
+    <div
+      v-if="errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/*/Error message.Title">
+          Error
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
     <div class="required field">
       <label for="current-name"><translate translate-context="*/*/*/Noun">Name</translate></label>
-      <input id="current-name" name="name" v-model="currentName" :placeholder="labels.namePlaceholder" required maxlength="100">
+      <input
+        id="current-name"
+        v-model="currentName"
+        name="name"
+        :placeholder="labels.namePlaceholder"
+        required
+        maxlength="100"
+      >
     </div>
     <div class="field">
       <label for="current-description"><translate translate-context="*/*/*/Noun">Description</translate></label>
-      <textarea id="current-description" v-model="currentDescription" :placeholder="labels.descriptionPlaceholder" maxlength="2000"></textarea>
+      <textarea
+        id="current-description"
+        v-model="currentDescription"
+        :placeholder="labels.descriptionPlaceholder"
+        maxlength="2000"
+      />
     </div>
     <div class="field">
       <label for="visibility-level"><translate translate-context="*/*/*">Visibility</translate></label>
-      <p><translate translate-context="Content/Library/Paragraph">You are able to share your library with other people, regardless of its visibility.</translate></p>
-      <select id="visibility-level" class="ui dropdown" v-model="currentVisibilityLevel">
-        <option :value="c" v-for="c in ['me', 'instance', 'everyone']">{{ sharedLabels.fields.privacy_level.choices[c] }}</option>
+      <p>
+        <translate translate-context="Content/Library/Paragraph">
+          You are able to share your library with other people, regardless of its visibility.
+        </translate>
+      </p>
+      <select
+        id="visibility-level"
+        v-model="currentVisibilityLevel"
+        class="ui dropdown"
+      >
+        <option
+          v-for="(c, key) in ['me', 'instance', 'everyone']"
+          :key="key"
+          :value="c"
+        >
+          {{ sharedLabels.fields.privacy_level.choices[c] }}
+        </option>
       </select>
     </div>
-    <button class="ui submit button" type="submit">
-      <translate translate-context="Content/Library/Button.Label/Verb" v-if="library">Update library</translate>
-      <translate translate-context="Content/Library/Button.Label/Verb" v-else>Create library</translate>
+    <button
+      class="ui submit button"
+      type="submit"
+    >
+      <translate
+        v-if="library"
+        translate-context="Content/Library/Button.Label/Verb"
+      >
+        Update library
+      </translate>
+      <translate
+        v-else
+        translate-context="Content/Library/Button.Label/Verb"
+      >
+        Create library
+      </translate>
     </button>
-    <dangerous-button v-if="library" type="button" class="ui right floated basic danger button" @confirm="remove()">
-      <translate translate-context="*/*/*/Verb">Delete</translate>
+    <dangerous-button
+      v-if="library"
+      type="button"
+      class="ui right floated basic danger button"
+      @confirm="remove()"
+    >
+      <translate translate-context="*/*/*/Verb">
+        Delete
+      </translate>
       <p slot="modal-header">
-        <translate translate-context="Popup/Library/Title">Delete this library?</translate>
+        <translate translate-context="Popup/Library/Title">
+          Delete this library?
+        </translate>
       </p>
       <p slot="modal-content">
         <translate translate-context="Popup/Library/Paragraph">
@@ -37,7 +105,9 @@
         </translate>
       </p>
       <div slot="modal-confirm">
-        <translate translate-context="Popup/Library/Button.Label/Verb">Delete library</translate>
+        <translate translate-context="Popup/Library/Button.Label/Verb">
+          Delete library
+        </translate>
       </div>
     </dangerous-button>
   </form>
@@ -49,9 +119,9 @@ import MixinsTranslation from '@/components/mixins/Translations.vue'
 
 export default {
   mixins: [MixinsTranslation],
-  props: ['library'],
+  props: { library: { type: Object, required: true } },
   data () {
-    let d = {
+    const d = {
       isLoading: false,
       over: false,
       errors: []
@@ -69,19 +139,19 @@ export default {
   },
   computed: {
     labels () {
-      let namePlaceholder = this.$pgettext('Content/Library/Input.Placeholder', 'My awesome library')
-      let descriptionPlaceholder = this.$pgettext('Content/Library/Input.Placeholder', 'This library contains my personal music, I hope you like it.')
+      const namePlaceholder = this.$pgettext('Content/Library/Input.Placeholder', 'My awesome library')
+      const descriptionPlaceholder = this.$pgettext('Content/Library/Input.Placeholder', 'This library contains my personal music, I hope you like it.')
       return {
         namePlaceholder,
-        descriptionPlaceholder,
+        descriptionPlaceholder
       }
     }
   },
   methods: {
     submit () {
-      let self = this
+      const self = this
       this.isLoading = true
-      let payload = {
+      const payload = {
         name: this.currentName,
         description: this.currentDescription,
         privacy_level: this.currentVisibilityLevel
@@ -117,10 +187,10 @@ export default {
       this.currentDescription = ''
     },
     remove () {
-      let self = this
+      const self = this
       axios.delete(`libraries/${this.library.uuid}/`).then((response) => {
         self.isLoading = false
-        let msg = this.$pgettext('Content/Library/Message', 'Library deleted')
+        const msg = this.$pgettext('Content/Library/Message', 'Library deleted')
         self.$emit('deleted', {})
         self.$store.commit('ui/addMessage', {
           content: msg,
diff --git a/front/src/views/content/libraries/Home.vue b/front/src/views/content/libraries/Home.vue
index 42fc5c77e..85b88ddd0 100644
--- a/front/src/views/content/libraries/Home.vue
+++ b/front/src/views/content/libraries/Home.vue
@@ -1,25 +1,62 @@
 <template>
   <section class="ui vertical aligned stripe segment">
-    <div v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']">
-      <div class="ui text loader"><translate translate-context="Content/Library/Paragraph">Loading Libraries…</translate></div>
+    <div
+      v-if="isLoading"
+      :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"
+    >
+      <div class="ui text loader">
+        <translate translate-context="Content/Library/Paragraph">
+          Loading Libraries…
+        </translate>
+      </div>
     </div>
-    <div v-else class="ui text container">
-      <h1 class="ui header"><translate translate-context="Content/Library/Title">My libraries</translate></h1>
+    <div
+      v-else
+      class="ui text container"
+    >
+      <h1 class="ui header">
+        <translate translate-context="Content/Library/Title">
+          My libraries
+        </translate>
+      </h1>
 
       <p v-if="libraries.length == 0">
-        <translate translate-context="Content/Library/Paragraph">Looks like you don't have a library, it's time to create one.</translate>
+        <translate translate-context="Content/Library/Paragraph">
+          Looks like you don't have a library, it's time to create one.
+        </translate>
       </p>
-      <a :aria-expanded="!hiddenForm" @click.prevent="hiddenForm = !hiddenForm" href="">
-        <i class="plus icon" v-if="hiddenForm" />
-        <i class="minus icon" v-else />
+      <a
+        :aria-expanded="!hiddenForm"
+        href=""
+        @click.prevent="hiddenForm = !hiddenForm"
+      >
+        <i
+          v-if="hiddenForm"
+          class="plus icon"
+        />
+        <i
+          v-else
+          class="minus icon"
+        />
         <translate translate-context="Content/Library/Link/Verb">Create a new library</translate>
       </a>
-      <library-form :library="null" v-if="!hiddenForm" @created="libraryCreated" />
-      <div class="ui hidden divider"></div>
+      <library-form
+        v-if="!hiddenForm"
+        :library="null"
+        @created="libraryCreated"
+      />
+      <div class="ui hidden divider" />
       <quota />
-      <div class="ui hidden divider"></div>
-      <div v-if="libraries.length > 0" class="ui two column grid">
-        <div v-for="library in libraries" :key="library.uuid" class="column">
+      <div class="ui hidden divider" />
+      <div
+        v-if="libraries.length > 0"
+        class="ui two column grid"
+      >
+        <div
+          v-for="library in libraries"
+          :key="library.uuid"
+          class="column"
+        >
           <library-card :library="library" />
         </div>
       </div>
@@ -28,32 +65,32 @@
 </template>
 
 <script>
-import axios from "axios"
-import LibraryForm from "./Form"
-import LibraryCard from "./Card"
-import Quota from "./Quota"
+import axios from 'axios'
+import LibraryForm from './Form'
+import LibraryCard from './Card'
+import Quota from './Quota'
 
 export default {
-  data() {
+  components: {
+    LibraryForm,
+    LibraryCard,
+    Quota
+  },
+  data () {
     return {
       isLoading: false,
       hiddenForm: true,
       libraries: []
     }
   },
-  created() {
+  created () {
     this.fetch()
   },
-  components: {
-    LibraryForm,
-    LibraryCard,
-    Quota
-  },
   methods: {
-    fetch() {
+    fetch () {
       this.isLoading = true
-      let self = this
-      axios.get("libraries/", {params: {scope: 'me'}}).then(response => {
+      const self = this
+      axios.get('libraries/', { params: { scope: 'me' } }).then(response => {
         self.isLoading = false
         self.libraries = response.data.results
         if (self.libraries.length === 0) {
@@ -61,8 +98,8 @@ export default {
         }
       })
     },
-    libraryCreated(library) {
-      this.$router.push({name: 'library.detail', params: {id: library.uuid}})
+    libraryCreated (library) {
+      this.$router.push({ name: 'library.detail', params: { id: library.uuid } })
     }
   }
 }
diff --git a/front/src/views/content/libraries/Quota.vue b/front/src/views/content/libraries/Quota.vue
index ada8351ea..4ebc5a5a7 100644
--- a/front/src/views/content/libraries/Quota.vue
+++ b/front/src/views/content/libraries/Quota.vue
@@ -1,92 +1,191 @@
 <template>
   <div class="ui segment">
-    <h3 class="ui header"><translate translate-context="Content/Library/Title">Current usage</translate></h3>
-    <div v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']">
-      <div class="ui text loader"><translate translate-context="Content/Library/Paragraph">Loading usage data…</translate></div>
+    <h3 class="ui header">
+      <translate translate-context="Content/Library/Title">
+        Current usage
+      </translate>
+    </h3>
+    <div
+      v-if="isLoading"
+      :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"
+    >
+      <div class="ui text loader">
+        <translate translate-context="Content/Library/Paragraph">
+          Loading usage data…
+        </translate>
+      </div>
     </div>
-    <div :class="['ui', {'success': progress < 60}, {'warning': progress >= 60 && progress < 96}, {'error': progress >= 95}, 'progress']" data-percent=progress>
-      <div class="bar" :style="{width: `${progress}%`}">
-        <div class="progress">{{ progress }}%</div>
+    <div
+      :class="['ui', {'success': progress < 60}, {'warning': progress >= 60 && progress < 96}, {'error': progress >= 95}, 'progress']"
+      data-percent="progress"
+    >
+      <div
+        class="bar"
+        :style="{width: `${progress}%`}"
+      >
+        <div class="progress">
+          {{ progress }}%
+        </div>
       </div>
-      <div class="label" v-if="quotaStatus">
-        <translate translate-context="Content/Library/Paragraph" :translate-params="{max: humanSize(quotaStatus.max * 1000 * 1000), current: humanSize(quotaStatus.current * 1000 * 1000)}">%{ current } used on %{ max } allowed</translate>
+      <div
+        v-if="quotaStatus"
+        class="label"
+      >
+        <translate
+          translate-context="Content/Library/Paragraph"
+          :translate-params="{max: humanSize(quotaStatus.max * 1000 * 1000), current: humanSize(quotaStatus.current * 1000 * 1000)}"
+        >
+          %{ current } used on %{ max } allowed
+        </translate>
       </div>
     </div>
-    <div class="ui hidden divider"></div>
-    <div v-if="quotaStatus" class="ui stackable three column grid">
-      <div v-if="quotaStatus.pending > 0" class="column">
+    <div class="ui hidden divider" />
+    <div
+      v-if="quotaStatus"
+      class="ui stackable three column grid"
+    >
+      <div
+        v-if="quotaStatus.pending > 0"
+        class="column"
+      >
         <div class="ui tiny warning statistic">
           <div class="value">
             {{ humanSize(quotaStatus.pending * 1000 * 1000) }}
           </div>
           <div class="label">
-            <translate translate-context="Content/Library/Label">Pending files</translate>
+            <translate translate-context="Content/Library/Label">
+              Pending files
+            </translate>
           </div>
         </div>
         <div>
           <router-link
             class="ui basic primary tiny button"
-            :to="{name: 'content.libraries.files', query: {q: compileTokens([{field: 'status', value: 'pending'}])}}">
-            <translate translate-context="Content/Library/Link/Verb">View files</translate>
+            :to="{name: 'content.libraries.files', query: {q: compileTokens([{field: 'status', value: 'pending'}])}}"
+          >
+            <translate translate-context="Content/Library/Link/Verb">
+              View files
+            </translate>
           </router-link>
 
           <dangerous-button
             class="ui basic tiny button"
-            :action="purgePendingFiles">
-            <translate translate-context="*/*/*/Verb">Purge</translate>
-            <p slot="modal-header"><translate translate-context="Popup/Library/Title">Purge pending files?</translate></p>
-            <p slot="modal-content"><translate translate-context="Popup/Library/Paragraph">Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota.</translate></p>
-            <div slot="modal-confirm"><translate translate-context="*/*/*/Verb">Purge</translate></div>
+            :action="purgePendingFiles"
+          >
+            <translate translate-context="*/*/*/Verb">
+              Purge
+            </translate>
+            <p slot="modal-header">
+              <translate translate-context="Popup/Library/Title">
+                Purge pending files?
+              </translate>
+            </p>
+            <p slot="modal-content">
+              <translate translate-context="Popup/Library/Paragraph">
+                Removes uploaded but yet to be processed tracks completely, adding the corresponding data to your quota.
+              </translate>
+            </p>
+            <div slot="modal-confirm">
+              <translate translate-context="*/*/*/Verb">
+                Purge
+              </translate>
+            </div>
           </dangerous-button>
         </div>
       </div>
-      <div v-if="quotaStatus.skipped > 0" class="column">
+      <div
+        v-if="quotaStatus.skipped > 0"
+        class="column"
+      >
         <div class="ui tiny statistic">
           <div class="value">
             {{ humanSize(quotaStatus.skipped * 1000 * 1000) }}
           </div>
           <div class="label">
-            <translate translate-context="Content/Library/Label">Skipped files</translate>
+            <translate translate-context="Content/Library/Label">
+              Skipped files
+            </translate>
           </div>
         </div>
         <div>
           <router-link
             class="ui basic primary tiny button"
-            :to="{name: 'content.libraries.files', query: {q: compileTokens([{field: 'status', value: 'skipped'}])}}">
-            <translate translate-context="Content/Library/Link/Verb">View files</translate>
+            :to="{name: 'content.libraries.files', query: {q: compileTokens([{field: 'status', value: 'skipped'}])}}"
+          >
+            <translate translate-context="Content/Library/Link/Verb">
+              View files
+            </translate>
           </router-link>
           <dangerous-button
             class="ui basic tiny button"
-            :action="purgeSkippedFiles">
-            <translate translate-context="*/*/*/Verb">Purge</translate>
-            <p slot="modal-header"><translate translate-context="Popup/Library/Title">Purge skipped files?</translate></p>
-            <p slot="modal-content"><translate translate-context="Popup/Library/Paragraph">Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota.</translate></p>
-            <div slot="modal-confirm"><translate translate-context="*/*/*/Verb">Purge</translate></div>
+            :action="purgeSkippedFiles"
+          >
+            <translate translate-context="*/*/*/Verb">
+              Purge
+            </translate>
+            <p slot="modal-header">
+              <translate translate-context="Popup/Library/Title">
+                Purge skipped files?
+              </translate>
+            </p>
+            <p slot="modal-content">
+              <translate translate-context="Popup/Library/Paragraph">
+                Removes uploaded tracks skipped during the import processes completely, adding the corresponding data to your quota.
+              </translate>
+            </p>
+            <div slot="modal-confirm">
+              <translate translate-context="*/*/*/Verb">
+                Purge
+              </translate>
+            </div>
           </dangerous-button>
         </div>
       </div>
-      <div v-if="quotaStatus.errored > 0" class="column">
+      <div
+        v-if="quotaStatus.errored > 0"
+        class="column"
+      >
         <div class="ui tiny danger statistic">
           <div class="value">
             {{ humanSize(quotaStatus.errored * 1000 * 1000) }}
           </div>
           <div class="label">
-            <translate translate-context="Content/Library/Label">Errored files</translate>
+            <translate translate-context="Content/Library/Label">
+              Errored files
+            </translate>
           </div>
         </div>
         <div>
           <router-link
             class="ui basic primary tiny button"
-            :to="{name: 'content.libraries.files', query: {q: compileTokens([{field: 'status', value: 'errored'}])}}">
-            <translate translate-context="Content/Library/Link/Verb">View files</translate>
+            :to="{name: 'content.libraries.files', query: {q: compileTokens([{field: 'status', value: 'errored'}])}}"
+          >
+            <translate translate-context="Content/Library/Link/Verb">
+              View files
+            </translate>
           </router-link>
           <dangerous-button
             class="ui basic tiny button"
-            :action="purgeErroredFiles">
-            <translate translate-context="*/*/*/Verb">Purge</translate>
-            <p slot="modal-header"><translate translate-context="Popup/Library/Title">Purge errored files?</translate></p>
-            <p slot="modal-content"><translate translate-context="Popup/Library/Paragraph">Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota.</translate></p>
-            <div slot="modal-confirm"><translate translate-context="*/*/*/Verb">Purge</translate></div>
+            :action="purgeErroredFiles"
+          >
+            <translate translate-context="*/*/*/Verb">
+              Purge
+            </translate>
+            <p slot="modal-header">
+              <translate translate-context="Popup/Library/Title">
+                Purge errored files?
+              </translate>
+            </p>
+            <p slot="modal-content">
+              <translate translate-context="Popup/Library/Paragraph">
+                Removes uploaded tracks that could not be processed by the server completely, adding the corresponding data to your quota.
+              </translate>
+            </p>
+            <div slot="modal-confirm">
+              <translate translate-context="*/*/*/Verb">
+                Purge
+              </translate>
+            </div>
           </dangerous-button>
         </div>
       </div>
@@ -95,8 +194,8 @@
 </template>
 <script>
 import axios from 'axios'
-import {humanSize} from '@/filters'
-import {compileTokens} from '@/search'
+import { humanSize } from '@/filters'
+import { compileTokens } from '@/search'
 
 export default {
   data () {
@@ -107,12 +206,20 @@ export default {
       compileTokens
     }
   },
+  computed: {
+    progress () {
+      if (!this.quotaStatus) {
+        return 0
+      }
+      return Math.min(parseInt(this.quotaStatus.current * 100 / this.quotaStatus.max), 100)
+    }
+  },
   created () {
     this.fetch()
   },
   methods: {
     fetch () {
-      let self = this
+      const self = this
       self.isLoading = true
       axios.get('users/me/').then((response) => {
         self.quotaStatus = response.data.quota_status
@@ -120,8 +227,8 @@ export default {
       })
     },
     purge (status) {
-      let self = this
-      let payload = {
+      const self = this
+      const payload = {
         action: 'delete',
         objects: 'all',
         filters: {
@@ -140,14 +247,6 @@ export default {
     },
     purgeErroredFiles () {
       this.purge('errored')
-    },
-  },
-  computed: {
-    progress () {
-      if (!this.quotaStatus) {
-        return 0
-      }
-      return Math.min(parseInt(this.quotaStatus.current * 100 / this.quotaStatus.max), 100)
     }
   }
 }
diff --git a/front/src/views/content/remote/Card.vue b/front/src/views/content/remote/Card.vue
index 3e082cee2..c66ed4d94 100644
--- a/front/src/views/content/remote/Card.vue
+++ b/front/src/views/content/remote/Card.vue
@@ -5,14 +5,18 @@
         <router-link :to="{name: 'library.detail', params: {id: library.uuid}}">
           {{ library.name }}
         </router-link>
-        <div class="ui right floated dropdown" v-dropdown>
-          <i class="ellipsis vertical large icon nomargin"></i>
+        <div
+          v-dropdown
+          class="ui right floated dropdown"
+        >
+          <i class="ellipsis vertical large icon nomargin" />
           <div class="menu">
             <button
               v-for="obj in getReportableObjs({library, account: library.actor})"
               :key="obj.target.type + obj.target.id"
               class="item basic"
-              @click.stop.prevent="$store.dispatch('moderation/report', obj.target)">
+              @click.stop.prevent="$store.dispatch('moderation/report', obj.target)"
+            >
               <i class="share icon" /> {{ obj.label }}
             </button>
           </div>
@@ -20,14 +24,16 @@
         <span
           v-if="library.privacy_level === 'me'"
           class="right floated"
-          :data-tooltip="labels.tooltips.me">
-          <i class="small lock icon"></i>
+          :data-tooltip="labels.tooltips.me"
+        >
+          <i class="small lock icon" />
         </span>
         <span
           v-else-if="library.privacy_level === 'everyone'"
           class="right floated"
-          :data-tooltip="labels.tooltips.everyone">
-          <i class="small globe icon"></i>
+          :data-tooltip="labels.tooltips.everyone"
+        >
+          <i class="small globe icon" />
         </span>
       </h4>
       <div class="meta">
@@ -38,92 +44,178 @@
       </div>
       <div class="description">
         {{ library.description }}
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
       </div>
       <div class="meta">
-        <i class="music icon"></i>
-        <translate translate-context="*/*/*" :translate-params="{count: library.uploads_count}" :translate-n="library.uploads_count" translate-plural="%{ count } tracks">%{ count } track</translate>
+        <i class="music icon" />
+        <translate
+          translate-context="*/*/*"
+          :translate-params="{count: library.uploads_count}"
+          :translate-n="library.uploads_count"
+          translate-plural="%{ count } tracks"
+        >
+          %{ count } track
+        </translate>
       </div>
-      <div v-if="displayScan && latestScan" class="meta">
+      <div
+        v-if="displayScan && latestScan"
+        class="meta"
+      >
         <template v-if="latestScan.status === 'pending'">
-          <i class="hourglass icon"></i>
-          <translate translate-context="Content/Library/Card.List item">Scan pending</translate>
+          <i class="hourglass icon" />
+          <translate translate-context="Content/Library/Card.List item">
+            Scan pending
+          </translate>
         </template>
         <template v-if="latestScan.status === 'scanning'">
-          <i class="loading spinner icon"></i>
-          <translate translate-context="Content/Library/Card.List item" :translate-params="{progress: scanProgress}">Scanning… (%{ progress }%)</translate>
+          <i class="loading spinner icon" />
+          <translate
+            translate-context="Content/Library/Card.List item"
+            :translate-params="{progress: scanProgress}"
+          >
+            Scanning… (%{ progress }%)
+          </translate>
         </template>
         <template v-else-if="latestScan.status === 'errored'">
-          <i class="dangerdownload icon"></i>
-          <translate translate-context="Content/Library/Card.List item">Problem during scanning</translate>
+          <i class="dangerdownload icon" />
+          <translate translate-context="Content/Library/Card.List item">
+            Problem during scanning
+          </translate>
         </template>
         <template v-else-if="latestScan.status === 'finished' && latestScan.errored_files === 0">
-          <i class="success download icon"></i>
-          <translate translate-context="Content/Library/Card.List item">Scanned</translate>
+          <i class="success download icon" />
+          <translate translate-context="Content/Library/Card.List item">
+            Scanned
+          </translate>
         </template>
         <template v-else-if="latestScan.status === 'finished' && latestScan.errored_files > 0">
-          <i class="warning download icon"></i>
-          <translate translate-context="Content/Library/Card.List item">Scanned with errors</translate>
+          <i class="warning download icon" />
+          <translate translate-context="Content/Library/Card.List item">
+            Scanned with errors
+          </translate>
         </template>
-        <a href="" class="link right floated" @click.prevent="showScan = !showScan">
+        <a
+          href=""
+          class="link right floated"
+          @click.prevent="showScan = !showScan"
+        >
           <translate translate-context="Content/Library/Card.Button.Label/Noun">Details</translate>
-          <i v-if="showScan" class="angle down icon" />
-          <i v-else class="angle right icon" />
+          <i
+            v-if="showScan"
+            class="angle down icon"
+          />
+          <i
+            v-else
+            class="angle right icon"
+          />
         </a>
         <div v-if="showScan">
           <template v-if="latestScan.modification_date">
-            <translate translate-context="Content/Library/Card.List item/Noun">Last update:</translate><human-date :date="latestScan.modification_date" /><br />
+            <translate translate-context="Content/Library/Card.List item/Noun">
+              Last update:
+            </translate><human-date :date="latestScan.modification_date" /><br>
           </template>
-          <translate translate-context="Content/Library/Card.List item/Noun">Failed tracks:</translate> {{ latestScan.errored_files }}
+          <translate translate-context="Content/Library/Card.List item/Noun">
+            Failed tracks:
+          </translate> {{ latestScan.errored_files }}
         </div>
       </div>
-      <div v-if="displayScan && canLaunchScan" class="clearfix">
-        <a href="" class="right floated link" @click.prevent="launchScan">
+      <div
+        v-if="displayScan && canLaunchScan"
+        class="clearfix"
+      >
+        <a
+          href=""
+          class="right floated link"
+          @click.prevent="launchScan"
+        >
           <translate translate-context="Content/Library/Card.Button.Label/Verb">Scan now</translate> <i class="paper plane icon" />
         </a>
       </div>
     </div>
     <div class="extra content">
-      <actor-link style="color: var(--link-color)" :actor="library.actor" />
+      <actor-link
+        style="color: var(--link-color)"
+        :actor="library.actor"
+      />
     </div>
-    <div v-if="displayCopyFid" class="extra content">
+    <div
+      v-if="displayCopyFid"
+      class="extra content"
+    >
       <div class="ui form">
         <div class="field">
           <label :for="library.fid"><translate translate-context="Content/Library/Title">Sharing link</translate></label>
-          <copy-input :id="library.fid" :button-classes="'basic'" :value="library.fid" />
+          <copy-input
+            :id="library.fid"
+            :button-classes="'basic'"
+            :value="library.fid"
+          />
         </div>
       </div>
     </div>
-    <div v-if="displayFollow || radioPlayable" :class="['ui', {two: displayFollow && radioPlayable}, 'bottom', 'attached', 'buttons']">
-      <radio-button v-if="radioPlayable" :type="'library'" :object-id="library.uuid"></radio-button>
+    <div
+      v-if="displayFollow || radioPlayable"
+      :class="['ui', {two: displayFollow && radioPlayable}, 'bottom', 'attached', 'buttons']"
+    >
+      <radio-button
+        v-if="radioPlayable"
+        :type="'library'"
+        :object-id="library.uuid"
+      />
       <template v-if="displayFollow">
         <button
           v-if="!library.follow"
+          :class="['ui', 'success', {'loading': isLoadingFollow}, 'button']"
           @click="follow()"
-          :class="['ui', 'success', {'loading': isLoadingFollow}, 'button']">
-          <translate translate-context="Content/Library/Card.Button.Label/Verb">Follow</translate>
+        >
+          <translate translate-context="Content/Library/Card.Button.Label/Verb">
+            Follow
+          </translate>
         </button>
         <template v-else-if="!library.follow.approved">
           <button
-            class="ui disabled button"><i class="hourglass icon"></i>
-            <translate translate-context="Content/Library/Card.Paragraph">Follow request pending approval</translate>
+            class="ui disabled button"
+          >
+            <i class="hourglass icon" />
+            <translate translate-context="Content/Library/Card.Paragraph">
+              Follow request pending approval
+            </translate>
           </button>
           <button
+            class="ui button"
             @click="unfollow"
-            class="ui button">
-            <translate translate-context="Content/Library/Card.Paragraph">Cancel follow request</translate>
+          >
+            <translate translate-context="Content/Library/Card.Paragraph">
+              Cancel follow request
+            </translate>
           </button>
         </template>
         <template v-else-if="library.follow.approved">
           <dangerous-button
             :class="['ui', 'button']"
-            :action="unfollow">
-            <translate translate-context="*/Library/Button.Label/Verb">Unfollow</translate>
-            <p slot="modal-header"><translate translate-context="Popup/Library/Title">Unfollow this library?</translate></p>
+            :action="unfollow"
+          >
+            <translate translate-context="*/Library/Button.Label/Verb">
+              Unfollow
+            </translate>
+            <p slot="modal-header">
+              <translate translate-context="Popup/Library/Title">
+                Unfollow this library?
+              </translate>
+            </p>
             <div slot="modal-content">
-              <p><translate translate-context="Popup/Library/Paragraph">By unfollowing this library, you loose access to its content.</translate></p>
+              <p>
+                <translate translate-context="Popup/Library/Paragraph">
+                  By unfollowing this library, you loose access to its content.
+                </translate>
+              </p>
+            </div>
+            <div slot="modal-confirm">
+              <translate translate-context="*/Library/Button.Label/Verb">
+                Unfollow
+              </translate>
             </div>
-            <div slot="modal-confirm"><translate translate-context="*/Library/Button.Label/Verb">Unfollow</translate></div>
           </dangerous-button>
         </template>
       </template>
@@ -134,31 +226,31 @@
 import axios from 'axios'
 import ReportMixin from '@/components/mixins/Report'
 import RadioButton from '@/components/radios/Button'
-import jQuery from 'jquery'
 
 export default {
-  mixins: [ReportMixin],
-  props: {
-    library: {type: Object, required: true},
-    displayFollow: {type: Boolean, default: true},
-    displayScan: {type: Boolean, default: true},
-    displayCopyFid: {type: Boolean, default: true},
-  },
   components: {
     RadioButton
   },
+  mixins: [ReportMixin],
+  props: {
+    initialLibrary: { type: Object, required: true },
+    displayFollow: { type: Boolean, default: true },
+    displayScan: { type: Boolean, default: true },
+    displayCopyFid: { type: Boolean, default: true }
+  },
   data () {
     return {
+      library: this.initialLibrary,
       isLoadingFollow: false,
       showScan: false,
       scanTimeout: null,
-      latestScan: this.library.latest_scan,
+      latestScan: this.library.latest_scan
     }
   },
   computed: {
     labels () {
-      let me = this.$pgettext('Content/Library/Card.Help text', 'This library is private and your approval from its owner is needed to access its content')
-      let everyone = this.$pgettext('Content/Library/Card.Help text', 'This library is public and you can access its content freely')
+      const me = this.$pgettext('Content/Library/Card.Help text', 'This library is private and your approval from its owner is needed to access its content')
+      const everyone = this.$pgettext('Content/Library/Card.Help text', 'This library is public and you can access its content freely')
 
       return {
         tooltips: {
@@ -168,8 +260,8 @@ export default {
       }
     },
     scanProgress () {
-      let scan = this.latestScan
-      let progress = scan.processed_files * 100 / scan.total_files
+      const scan = this.latestScan
+      const progress = scan.processed_files * 100 / scan.total_files
       return Math.min(parseInt(progress), 100)
     },
     scanStatus () {
@@ -192,16 +284,29 @@ export default {
         (this.library.actor.is_local || this.scanStatus === 'finished') &&
         (this.library.privacy_level === 'everyone' || (this.library.follow && this.library.follow.is_approved))
       )
-    },
+    }
+  },
+  watch: {
+    showScan (newValue, oldValue) {
+      if (newValue) {
+        if (this.scanStatus === 'pending' || this.scanStatus === 'scanning') {
+          this.fetchScanStatus()
+        }
+      } else {
+        if (this.scanTimeout) {
+          clearTimeout(this.scanTimeout)
+        }
+      }
+    }
   },
   methods: {
     launchScan () {
-      let self = this
-      let successMsg = this.$pgettext('Content/Library/Message', 'Scan launched')
-      let skippedMsg = this.$pgettext('Content/Library/Message', 'Scan skipped (previous scan is too recent)')
+      const self = this
+      const successMsg = this.$pgettext('Content/Library/Message', 'Scan launched')
+      const skippedMsg = this.$pgettext('Content/Library/Message', 'Scan skipped (previous scan is too recent)')
       axios.post(`federation/libraries/${this.library.uuid}/scan/`).then((response) => {
         let msg
-        if (response.data.status == 'skipped') {
+        if (response.data.status === 'skipped') {
           msg = skippedMsg
         } else {
           self.latestScan = response.data.scan
@@ -214,19 +319,22 @@ export default {
       })
     },
     follow () {
-      let self = this
+      const self = this
       this.isLoadingFollow = true
-      axios.post('federation/follows/library/', {target: this.library.uuid}).then((response) => {
+      axios.post('federation/follows/library/', { target: this.library.uuid }).then((response) => {
         self.library.follow = response.data
         self.isLoadingFollow = false
         self.$emit('followed')
-
       }, error => {
         self.isLoadingFollow = false
+        self.$store.commit('ui/addMessage', {
+          content: 'Cannot follow remote library: ' + error,
+          date: new Date()
+        })
       })
     },
     unfollow () {
-      let self = this
+      const self = this
       this.isLoadingFollow = true
       axios.delete(`federation/follows/library/${this.library.follow.uuid}/`).then((response) => {
         self.$emit('deleted')
@@ -234,10 +342,14 @@ export default {
         self.isLoadingFollow = false
       }, error => {
         self.isLoadingFollow = false
+        self.$store.commit('ui/addMessage', {
+          content: 'Cannot unfollow remote library: ' + error,
+          date: new Date()
+        })
       })
     },
     fetchScanStatus () {
-      let self = this
+      const self = this
       axios.get(`federation/follows/library/${this.library.follow.uuid}/`).then((response) => {
         self.latestScan = response.data.target.latest_scan
         if (self.scanStatus === 'pending' || self.scanStatus === 'scanning') {
@@ -247,19 +359,6 @@ export default {
         }
       })
     }
-  },
-  watch: {
-    showScan (newValue, oldValue) {
-      if (newValue) {
-        if (this.scanStatus === 'pending' || this.scanStatus === 'scanning') {
-          this.fetchScanStatus()
-        }
-      } else {
-        if (this.scanTimeout) {
-          clearTimeout(this.scanTimeout)
-        }
-      }
-    }
   }
 }
 </script>
diff --git a/front/src/views/content/remote/Home.vue b/front/src/views/content/remote/Home.vue
index 0a896c9de..eb24bb1b6 100644
--- a/front/src/views/content/remote/Home.vue
+++ b/front/src/views/content/remote/Home.vue
@@ -1,29 +1,63 @@
 <template>
   <div class="ui vertical aligned stripe segment">
-    <div v-if="isLoading" :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']">
-      <div class="ui text loader"><translate translate-context="Content/Library/Paragraph">Loading remote libraries…</translate></div>
+    <div
+      v-if="isLoading"
+      :class="['ui', {'active': isLoading}, 'inverted', 'dimmer']"
+    >
+      <div class="ui text loader">
+        <translate translate-context="Content/Library/Paragraph">
+          Loading remote libraries…
+        </translate>
+      </div>
     </div>
-    <div v-else class="ui text container">
-      <h1 class="ui header"><translate translate-context="Content/Library/Title/Noun">Remote libraries</translate></h1>
-      <p><translate translate-context="Content/Library/Paragraph">Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access.</translate></p>
-      <scan-form @scanned="scanResult = $event"></scan-form>
-      <div class="ui hidden divider"></div>
-      <div v-if="scanResult && scanResult.results.length > 0" class="ui two cards">
-        <library-card :library="library" v-for="library in scanResult.results" :key="library.fid" />
+    <div
+      v-else
+      class="ui text container"
+    >
+      <h1 class="ui header">
+        <translate translate-context="Content/Library/Title/Noun">
+          Remote libraries
+        </translate>
+      </h1>
+      <p>
+        <translate translate-context="Content/Library/Paragraph">
+          Remote libraries are owned by other users on the network. You can access them as long as they are public or you are granted access.
+        </translate>
+      </p>
+      <scan-form @scanned="scanResult = $event" />
+      <div class="ui hidden divider" />
+      <div
+        v-if="scanResult && scanResult.results.length > 0"
+        class="ui two cards"
+      >
+        <library-card
+          v-for="library in scanResult.results"
+          :key="library.fid"
+          :library="library"
+        />
       </div>
       <template v-if="existingFollows && existingFollows.count > 0">
-        <h2><translate translate-context="Content/Library/Title">Known libraries</translate></h2>
-        <a href="" class="discrete link" @click.prevent="fetch()" >
+        <h2>
+          <translate translate-context="Content/Library/Title">
+            Known libraries
+          </translate>
+        </h2>
+        <a
+          href=""
+          class="discrete link"
+          @click.prevent="fetch()"
+        >
           <i :class="['ui', 'circular', 'refresh', 'icon']" /> <translate translate-context="Content/*/Button.Label/Short, Verb">Refresh</translate>
         </a>
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
         <div class="ui two cards">
           <library-card
+            v-for="follow in existingFollows.results"
+            :key="follow.fid"
+            :library="getLibraryFromFollow(follow)"
             @deleted="fetch()"
             @followed="fetch()"
-            :library="getLibraryFromFollow(follow)"
-            v-for="follow in existingFollows.results"
-            :key="follow.fid" />
+          />
         </div>
       </template>
     </div>
@@ -36,25 +70,26 @@ import ScanForm from './ScanForm'
 import LibraryCard from './Card'
 
 export default {
+  components: {
+    ScanForm,
+    LibraryCard
+  },
   data () {
     return {
       isLoading: false,
       scanResult: null,
-      existingFollows: null
+      existingFollows: null,
+      errors: []
     }
   },
   created () {
     this.fetch()
   },
-  components: {
-    ScanForm,
-    LibraryCard
-  },
   methods: {
     fetch () {
       this.isLoading = true
-      let self = this
-      axios.get('federation/follows/library/', {params: {'page_size': 100, 'ordering': '-creation_date'}}).then((response) => {
+      const self = this
+      axios.get('federation/follows/library/', { params: { page_size: 100, ordering: '-creation_date' } }).then((response) => {
         self.existingFollows = response.data
         self.existingFollows.results.forEach(f => {
           f.target.follow = f
@@ -62,10 +97,11 @@ export default {
         self.isLoading = false
       }, error => {
         self.isLoading = false
+        self.errors.push(error)
       })
     },
     getLibraryFromFollow (follow) {
-      let d = follow.target
+      const d = follow.target
       d.follow = follow
       return d
     }
diff --git a/front/src/views/content/remote/ScanForm.vue b/front/src/views/content/remote/ScanForm.vue
index af5fa6e40..3d6533c5b 100644
--- a/front/src/views/content/remote/ScanForm.vue
+++ b/front/src/views/content/remote/ScanForm.vue
@@ -1,17 +1,43 @@
 <template>
-  <form class="ui form" @submit.prevent="scan">
-    <div v-if="errors.length > 0" role="alert" class="ui negative message">
-      <h4 class="header"><translate translate-context="Content/Library/Error message.Title">Could not fetch remote library</translate></h4>
+  <form
+    class="ui form"
+    @submit.prevent="scan"
+  >
+    <div
+      v-if="errors.length > 0"
+      role="alert"
+      class="ui negative message"
+    >
+      <h4 class="header">
+        <translate translate-context="Content/Library/Error message.Title">
+          Could not fetch remote library
+        </translate>
+      </h4>
       <ul class="list">
-        <li v-for="error in errors">{{ error }}</li>
+        <li
+          v-for="(error, key) in errors"
+          :key="key"
+        >
+          {{ error }}
+        </li>
       </ul>
     </div>
     <div class="ui field">
       <label for="library-search"><translate translate-context="Content/Library/Input.Label/Verb">Search a remote library</translate></label>
       <div :class="['ui', 'action', {loading: isLoading}, 'input']">
-        <input id="library-search" name="url" v-model="query" :placeholder="labels.placeholder" type="url">
-        <button :aria-label="labels.submitLibrarySearch" type="submit" :class="['ui', 'icon', {loading: isLoading}, 'button']">
-          <i class="search icon"></i>
+        <input
+          id="library-search"
+          v-model="query"
+          name="url"
+          :placeholder="labels.placeholder"
+          type="url"
+        >
+        <button
+          :aria-label="labels.submitLibrarySearch"
+          type="submit"
+          :class="['ui', 'icon', {loading: isLoading}, 'button']"
+        >
+          <i class="search icon" />
         </button>
       </div>
     </div>
@@ -28,15 +54,23 @@ export default {
       errors: []
     }
   },
+  computed: {
+    labels () {
+      return {
+        placeholder: this.$pgettext('Content/Library/Input.Placeholder', 'Enter a library URL'),
+        submitLibrarySearch: this.$pgettext('Content/Library/Input.Label', 'Submit search')
+      }
+    }
+  },
   methods: {
     scan () {
       if (!this.query) {
         return
       }
-      let self = this
+      const self = this
       self.errors = []
       self.isLoading = true
-      axios.post('federation/libraries/fetch/', {fid: this.query}).then((response) => {
+      axios.post('federation/libraries/fetch/', { fid: this.query }).then((response) => {
         self.$emit('scanned', response.data)
         self.isLoading = false
       }, error => {
@@ -44,14 +78,6 @@ export default {
         self.errors = error.backendErrors
       })
     }
-  },
-  computed: {
-    labels () {
-      return {
-        placeholder: this.$pgettext('Content/Library/Input.Placeholder', 'Enter a library URL'),
-        submitLibrarySearch: this.$pgettext('Content/Library/Input.Label', 'Submit search')
-      }
-    }
   }
 }
 </script>
diff --git a/front/src/views/library/DetailAlbums.vue b/front/src/views/library/DetailAlbums.vue
index 35dec511b..713eaccea 100644
--- a/front/src/views/library/DetailAlbums.vue
+++ b/front/src/views/library/DetailAlbums.vue
@@ -5,11 +5,24 @@
       :header="false"
       :search="true"
       :controls="false"
-      :filters="{playable: true, ordering: '-creation_date', library: object.uuid}">
+      :filters="{playable: true, ordering: '-creation_date', library: object.uuid}"
+    >
       <empty-state slot="empty-state">
         <p>
-          <translate key="1" v-if="isOwner" translate-context="*/*/*">This library is empty, you should upload something in it!</translate>
-          <translate key="2" v-else translate-context="*/*/*">You may need to follow this library to see its content.</translate>
+          <translate
+            v-if="isOwner"
+            key="1"
+            translate-context="*/*/*"
+          >
+            This library is empty, you should upload something in it!
+          </translate>
+          <translate
+            v-else
+            key="2"
+            translate-context="*/*/*"
+          >
+            You may need to follow this library to see its content.
+          </translate>
         </p>
       </empty-state>
     </album-widget>
@@ -17,12 +30,15 @@
 </template>
 
 <script>
-import AlbumWidget from "@/components/audio/album/Widget"
+import AlbumWidget from '@/components/audio/album/Widget'
 
 export default {
-  props: ['object', 'isOwner'],
   components: {
-    AlbumWidget,
+    AlbumWidget
   },
+  props: {
+    object: { type: String, required: true },
+    isOwner: { type: Boolean, required: true }
+  }
 }
 </script>
diff --git a/front/src/views/library/DetailBase.vue b/front/src/views/library/DetailBase.vue
index c570be1e7..88b5cbc3b 100644
--- a/front/src/views/library/DetailBase.vue
+++ b/front/src/views/library/DetailBase.vue
@@ -1,77 +1,136 @@
 <template>
   <main v-title="labels.title">
     <div class="ui vertical stripe segment container">
-      <div v-if="isLoading" class="ui centered active inline loader"></div>
-      <div class="ui stackable grid" v-else-if="object">
+      <div
+        v-if="isLoading"
+        class="ui centered active inline loader"
+      />
+      <div
+        v-else-if="object"
+        class="ui stackable grid"
+      >
         <div class="ui five wide column">
-          <button class="ui pointing dropdown icon small basic right floated button" ref="dropdown" v-dropdown="{direction: 'downward'}" style="position: absolute; right: 1em; top: 1em;">
-            <i class="ellipsis vertical icon"></i>
+          <button
+            ref="dropdown"
+            v-dropdown="{direction: 'downward'}"
+            class="ui pointing dropdown icon small basic right floated button"
+            style="position: absolute; right: 1em; top: 1em;"
+          >
+            <i class="ellipsis vertical icon" />
             <div class="menu">
               <a
-                :href="object.fid"
                 v-if="object.actor.domain != $store.getters['instance/domain']"
+                :href="object.fid"
                 target="_blank"
-                class="basic item">
-                <i class="external icon"></i>
-                <translate :translate-params="{domain: object.actor.domain}" translate-context="Content/*/Button.Label/Verb">View on %{ domain }</translate>
+                class="basic item"
+              >
+                <i class="external icon" />
+                <translate
+                  :translate-params="{domain: object.actor.domain}"
+                  translate-context="Content/*/Button.Label/Verb"
+                >View on %{ domain }</translate>
               </a>
               <div
-                role="button"
-                class="basic item"
                 v-for="obj in getReportableObjs({library: object})"
                 :key="obj.target.type + obj.target.id"
-                @click.stop.prevent="$store.dispatch('moderation/report', obj.target)">
+                role="button"
+                class="basic item"
+                @click.stop.prevent="$store.dispatch('moderation/report', obj.target)"
+              >
                 <i class="share icon" /> {{ obj.label }}
               </div>
 
-              <div class="divider"></div>
-              <router-link class="basic item" v-if="$store.state.auth.availablePermissions['moderation']" :to="{name: 'manage.library.libraries.detail', params: {id: object.uuid}}">
-                <i class="wrench icon"></i>
-                <translate translate-context="Content/Moderation/Link">Open in moderation interface</translate>
+              <div class="divider" />
+              <router-link
+                v-if="$store.state.auth.availablePermissions['moderation']"
+                class="basic item"
+                :to="{name: 'manage.library.libraries.detail', params: {id: object.uuid}}"
+              >
+                <i class="wrench icon" />
+                <translate translate-context="Content/Moderation/Link">
+                  Open in moderation interface
+                </translate>
               </router-link>
             </div>
           </button>
           <h1 class="ui header">
-            <div class="ui hidden divider"></div>
+            <div class="ui hidden divider" />
             <div class="ellipsis content">
-              <i class="layer group small icon"></i>
+              <i class="layer group small icon" />
               <span>{{ object.name }}</span>
-              <div class="ui very small hidden divider"></div>
-              <div class="sub header ellipsis" :title="object.full_username">
-                <actor-link :avatar="false" :actor="object.actor" :truncate-length="0">
-                  <translate translate-context="*/*/*" :translate-params="{username: object.actor.full_username}">Owned by %{ username }</translate>
+              <div class="ui very small hidden divider" />
+              <div
+                class="sub header ellipsis"
+                :title="object.full_username"
+              >
+                <actor-link
+                  :avatar="false"
+                  :actor="object.actor"
+                  :truncate-length="0"
+                >
+                  <translate
+                    translate-context="*/*/*"
+                    :translate-params="{username: object.actor.full_username}"
+                  >
+                    Owned by %{ username }
+                  </translate>
                 </actor-link>
               </div>
             </div>
           </h1>
           <p>
-            <span v-if="object.privacy_level === 'me'" :title="labels.tooltips.me">
-              <i class="lock icon"></i>
+            <span
+              v-if="object.privacy_level === 'me'"
+              :title="labels.tooltips.me"
+            >
+              <i class="lock icon" />
               {{ labels.visibility.me }}
             </span>
             <span
-              v-else-if="object.privacy_level === 'instance'" :title="labels.tooltips.instance">
-              <i class="lock open icon"></i>
+              v-else-if="object.privacy_level === 'instance'"
+              :title="labels.tooltips.instance"
+            >
+              <i class="lock open icon" />
               {{ labels.visibility.instance }}
             </span>
-            <span v-else-if="object.privacy_level === 'everyone'" :title="labels.tooltips.everyone">
-              <i class="globe icon"></i>
+            <span
+              v-else-if="object.privacy_level === 'everyone'"
+              :title="labels.tooltips.everyone"
+            >
+              <i class="globe icon" />
               {{ labels.visibility.everyone }}
             </span> ·
-            <i class="music icon"></i>
-            <translate translate-context="*/*/*" :translate-params="{count: object.uploads_count}" :translate-n="object.uploads_count" translate-plural="%{ count } tracks">%{ count } track</translate>
+            <i class="music icon" />
+            <translate
+              translate-context="*/*/*"
+              :translate-params="{count: object.uploads_count}"
+              :translate-n="object.uploads_count"
+              translate-plural="%{ count } tracks"
+            >
+              %{ count } track
+            </translate>
             <span v-if="object.size">
-              · <i class="database icon"></i>
+              · <i class="database icon" />
               {{ object.size | humanSize }}
             </span>
           </p>
 
           <div class="header-buttons">
             <div class="ui small buttons">
-              <radio-button :disabled="!isPlayable" type="library" :object-id="object.uuid"></radio-button>
+              <radio-button
+                :disabled="!isPlayable"
+                type="library"
+                :object-id="object.uuid"
+              />
             </div>
-            <div class="ui small buttons" v-if="!isOwner">
-              <library-follow-button v-if="$store.state.auth.authenticated" :library="object"></library-follow-button>
+            <div
+              v-if="!isOwner"
+              class="ui small buttons"
+            >
+              <library-follow-button
+                v-if="$store.state.auth.authenticated"
+                :library="object"
+              />
             </div>
           </div>
 
@@ -79,15 +138,20 @@
             <rendered-description
               :content="object.description ? {html: object.description} : null"
               :update-url="`channels/${object.uuid}/`"
-              :can-update="false"></rendered-description>
-              <div class="ui hidden divider"></div>
+              :can-update="false"
+            />
+            <div class="ui hidden divider" />
           </template>
           <div class="ui form">
             <div class="field">
               <label for="copy-input">
                 <translate translate-context="Content/Library/Title">Sharing link</translate>
               </label>
-              <p><translate translate-context="Content/Library/Paragraph">Share this link with other users so they can request access to this library by copy-pasting it in their pod search bar.</translate></p>
+              <p>
+                <translate translate-context="Content/Library/Paragraph">
+                  Share this link with other users so they can request access to this library by copy-pasting it in their pod search bar.
+                </translate>
+              </p>
               <copy-input :value="object.fid" />
             </div>
           </div>
@@ -96,30 +160,63 @@
           <div class="ui head vertical stripe segment">
             <div class="ui container">
               <div class="ui secondary pointing center aligned menu">
-                <router-link class="item" :exact="true" :to="{name: 'library.detail'}">
-                  <translate translate-context="*/*/*">Artists</translate>
+                <router-link
+                  class="item"
+                  :exact="true"
+                  :to="{name: 'library.detail'}"
+                >
+                  <translate translate-context="*/*/*">
+                    Artists
+                  </translate>
                 </router-link>
-                <router-link class="item" :exact="true" :to="{name: 'library.detail.albums'}">
-                  <translate translate-context="*/*/*">Albums</translate>
+                <router-link
+                  class="item"
+                  :exact="true"
+                  :to="{name: 'library.detail.albums'}"
+                >
+                  <translate translate-context="*/*/*">
+                    Albums
+                  </translate>
                 </router-link>
-                <router-link class="item" :exact="true" :to="{name: 'library.detail.tracks'}">
-                  <translate translate-context="*/*/*">Tracks</translate>
+                <router-link
+                  class="item"
+                  :exact="true"
+                  :to="{name: 'library.detail.tracks'}"
+                >
+                  <translate translate-context="*/*/*">
+                    Tracks
+                  </translate>
                 </router-link>
-                <router-link v-if="isOwner" class="item" :exact="true" :to="{name: 'library.detail.upload'}">
-                  <i class="upload icon"></i>
-                  <translate translate-context="Content/Library/Card.Button.Label/Verb">Upload</translate>
+                <router-link
+                  v-if="isOwner"
+                  class="item"
+                  :exact="true"
+                  :to="{name: 'library.detail.upload'}"
+                >
+                  <i class="upload icon" />
+                  <translate translate-context="Content/Library/Card.Button.Label/Verb">
+                    Upload
+                  </translate>
                 </router-link>
-                <router-link v-if="isOwner" class="item" :exact="true" :to="{name: 'library.detail.edit'}">
-                  <i class="pencil icon"></i>
-                  <translate translate-context="Content/*/Button.Label/Verb">Edit</translate>
+                <router-link
+                  v-if="isOwner"
+                  class="item"
+                  :exact="true"
+                  :to="{name: 'library.detail.edit'}"
+                >
+                  <i class="pencil icon" />
+                  <translate translate-context="Content/*/Button.Label/Verb">
+                    Edit
+                  </translate>
                 </router-link>
               </div>
-              <div class="ui hidden divider"></div>
-                <router-view
-                  @updated="fetchData"
-                  @uploads-finished="object.uploads_count += $event"
-                  :is-owner="isOwner"
-                  :object="object"></router-view>
+              <div class="ui hidden divider" />
+              <router-view
+                :is-owner="isOwner"
+                :object="object"
+                @updated="fetchData"
+                @uploads-finished="object.uploads_count += $event"
+              />
             </div>
           </div>
         </div>
@@ -129,49 +226,29 @@
 </template>
 
 <script>
-import axios from "axios"
-import PlayButton from "@/components/audio/PlayButton"
-import LibraryFollowButton from "@/components/audio/LibraryFollowButton"
+import axios from 'axios'
+import LibraryFollowButton from '@/components/audio/LibraryFollowButton'
 import ReportMixin from '@/components/mixins/Report'
 import RadioButton from '@/components/radios/Button'
 
 export default {
-  mixins: [ReportMixin],
-  props: ["id"],
   components: {
-    PlayButton,
     RadioButton,
     LibraryFollowButton
   },
-  data() {
-    return {
-      isLoading: true,
-      object: null,
-      latestTracks: null,
-    }
-  },
+  mixins: [ReportMixin],
   beforeRouteUpdate (to, from, next) {
     to.meta.preserveScrollPosition = true
     next()
   },
-  async created() {
-    await this.fetchData()
-    let authenticated = this.$store.state.auth.authenticated
-    if (!authenticated && this.$store.getters['instance/domain'] != this.object.actor.domain) {
-      this.$router.push({name: 'login', query: {next: this.$route.fullPath}})
+  props: { id: { type: String, required: true } },
+  data () {
+    return {
+      isLoading: true,
+      object: null,
+      latestTracks: null
     }
   },
-  methods: {
-    async fetchData() {
-      var self = this
-      this.isLoading = true
-      let libraryPromise = axios.get(`libraries/${this.id}`).then(response => {
-        self.object = response.data
-      })
-      await libraryPromise
-      self.isLoading = false
-    },
-  },
   computed: {
     isOwner () {
       return this.$store.state.auth.authenticated && this.object.actor.full_username === this.$store.state.auth.fullUsername
@@ -182,12 +259,12 @@ export default {
         visibility: {
           me: this.$pgettext('Content/Library/Card.Help text', 'Private'),
           instance: this.$pgettext('Content/Library/Card.Help text', 'Restricted'),
-          everyone: this.$pgettext('Content/Library/Card.Help text', 'Public'),
+          everyone: this.$pgettext('Content/Library/Card.Help text', 'Public')
         },
         tooltips: {
           me: this.$pgettext('Content/Library/Card.Help text', 'This library is private and your approval from its owner is needed to access its content'),
           instance: this.$pgettext('Content/Library/Card.Help text', 'This library is restricted to users on this pod only'),
-          everyone: this.$pgettext('Content/Library/Card.Help text', 'This library is public and you can access its content freely'),
+          everyone: this.$pgettext('Content/Library/Card.Help text', 'This library is public and you can access its content freely')
         }
       }
     },
@@ -198,12 +275,30 @@ export default {
         (this.object.privacy_level === 'instance' && this.$store.state.auth.authenticated && this.object.actor.domain === this.$store.getters['instance/domain']) ||
         (this.$store.getters['libraries/follow'](this.object.uuid) || {}).approved === true
       )
-    },
+    }
   },
   watch: {
-    id() {
+    id () {
       this.fetchData()
     }
+  },
+  async created () {
+    await this.fetchData()
+    const authenticated = this.$store.state.auth.authenticated
+    if (!authenticated && this.$store.getters['instance/domain'] !== this.object.actor.domain) {
+      this.$router.push({ name: 'login', query: { next: this.$route.fullPath } })
+    }
+  },
+  methods: {
+    async fetchData () {
+      const self = this
+      this.isLoading = true
+      const libraryPromise = axios.get(`libraries/${this.id}`).then(response => {
+        self.object = response.data
+      })
+      await libraryPromise
+      self.isLoading = false
+    }
   }
 }
 </script>
diff --git a/front/src/views/library/DetailOverview.vue b/front/src/views/library/DetailOverview.vue
index facbfb708..14db9f12a 100644
--- a/front/src/views/library/DetailOverview.vue
+++ b/front/src/views/library/DetailOverview.vue
@@ -4,8 +4,9 @@
       <rendered-description
         :content="object.description ? {html: object.description} : null"
         :update-url="`channels/${object.uuid}/`"
-        :can-update="false"></rendered-description>
-        <div class="ui hidden divider"></div>
+        :can-update="false"
+      />
+      <div class="ui hidden divider" />
     </template>
     <artist-widget
       :key="object.uploads_count"
@@ -13,11 +14,24 @@
       :header="false"
       :search="true"
       :controls="false"
-      :filters="{playable: true, ordering: '-creation_date', library: object.uuid}">
+      :filters="{playable: true, ordering: '-creation_date', library: object.uuid}"
+    >
       <empty-state slot="empty-state">
         <p>
-          <translate key="1" v-if="isOwner" translate-context="*/*/*">This library is empty, you should upload something in it!</translate>
-          <translate key="2" v-else translate-context="*/*/*">You may need to follow this library to see its content.</translate>
+          <translate
+            v-if="isOwner"
+            key="1"
+            translate-context="*/*/*"
+          >
+            This library is empty, you should upload something in it!
+          </translate>
+          <translate
+            v-else
+            key="2"
+            translate-context="*/*/*"
+          >
+            You may need to follow this library to see its content.
+          </translate>
         </p>
       </empty-state>
     </artist-widget>
@@ -25,15 +39,18 @@
 </template>
 
 <script>
-import ArtistWidget from "@/components/audio/artist/Widget"
+import ArtistWidget from '@/components/audio/artist/Widget'
 
 export default {
-  props: ['object', 'isOwner'],
   components: {
-    ArtistWidget,
+    ArtistWidget
+  },
+  props: {
+    object: { type: String, required: true },
+    isOwner: { type: Boolean, required: true }
   },
   data () {
-    return  {
+    return {
       query: ''
     }
   }
diff --git a/front/src/views/library/DetailTracks.vue b/front/src/views/library/DetailTracks.vue
index 0fa1869a9..c9dd17bb7 100644
--- a/front/src/views/library/DetailTracks.vue
+++ b/front/src/views/library/DetailTracks.vue
@@ -4,11 +4,24 @@
       :key="object.uploads_count"
       :display-actions="false"
       :search="true"
-      :filters="{playable: true, library: object.uuid, ordering: '-creation_date'}">
+      :filters="{playable: true, library: object.uuid, ordering: '-creation_date'}"
+    >
       <empty-state slot="empty-state">
         <p>
-          <translate key="1" v-if="isOwner" translate-context="*/*/*">This library is empty, you should upload something in it!</translate>
-          <translate key="2" v-else translate-context="*/*/*">You may need to follow this library to see its content.</translate>
+          <translate
+            v-if="isOwner"
+            key="1"
+            translate-context="*/*/*"
+          >
+            This library is empty, you should upload something in it!
+          </translate>
+          <translate
+            v-else
+            key="2"
+            translate-context="*/*/*"
+          >
+            You may need to follow this library to see its content.
+          </translate>
         </p>
       </empty-state>
     </track-table>
@@ -19,9 +32,12 @@
 import TrackTable from '@/components/audio/track/Table'
 
 export default {
-  props: ['object', 'isOwner'],
   components: {
-    TrackTable,
+    TrackTable
   },
+  props: {
+    object: { type: String, required: true },
+    isOwner: { type: Boolean, required: true }
+  }
 }
 </script>
diff --git a/front/src/views/library/Edit.vue b/front/src/views/library/Edit.vue
index 998e364c1..00ba50c71 100644
--- a/front/src/views/library/Edit.vue
+++ b/front/src/views/library/Edit.vue
@@ -1,93 +1,153 @@
 <template>
   <section>
-    <library-form :library="object" @updated="$emit('updated')" @deleted="$router.push({name: 'profile.overview', params: {username: $store.state.auth.username}})" />
-    <div class="ui hidden divider"></div>
+    <library-form
+      :library="object"
+      @updated="$emit('updated')"
+      @deleted="$router.push({name: 'profile.overview', params: {username: $store.state.auth.username}})"
+    />
+    <div class="ui hidden divider" />
     <h2 class="ui header">
-      <translate translate-context="*/*/*">Library contents</translate>
+      <translate translate-context="*/*/*">
+        Library contents
+      </translate>
     </h2>
-    <library-files-table :filters="{library: object.uuid}"></library-files-table>
+    <library-files-table :filters="{library: object.uuid}" />
 
-    <div class="ui hidden divider"></div>
+    <div class="ui hidden divider" />
     <h2 class="ui header">
-      <translate translate-context="Content/Federation/*/Noun">Followers</translate>
+      <translate translate-context="Content/Federation/*/Noun">
+        Followers
+      </translate>
     </h2>
-    <div v-if="isLoadingFollows" :class="['ui', {'active': isLoadingFollows}, 'inverted', 'dimmer']">
-      <div class="ui text loader"><translate translate-context="Content/Library/Paragraph">Loading followers…</translate></div>
+    <div
+      v-if="isLoadingFollows"
+      :class="['ui', {'active': isLoadingFollows}, 'inverted', 'dimmer']"
+    >
+      <div class="ui text loader">
+        <translate translate-context="Content/Library/Paragraph">
+          Loading followers…
+        </translate>
+      </div>
     </div>
-    <table v-else-if="follows && follows.count > 0" class="ui table">
+    <table
+      v-else-if="follows && follows.count > 0"
+      class="ui table"
+    >
       <thead>
         <tr>
-          <th><translate translate-context="Content/Library/Table.Label">User</translate></th>
-          <th><translate translate-context="Content/Library/Table.Label">Date</translate></th>
-          <th><translate translate-context="*/*/*">Status</translate></th>
-          <th><translate translate-context="Content/Library/Table.Label">Action</translate></th>
+          <th>
+            <translate translate-context="Content/Library/Table.Label">
+              User
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Library/Table.Label">
+              Date
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="*/*/*">
+              Status
+            </translate>
+          </th>
+          <th>
+            <translate translate-context="Content/Library/Table.Label">
+              Action
+            </translate>
+          </th>
         </tr>
       </thead>
-      <tr v-for="follow in follows.results" :key="follow.fid">
+      <tr
+        v-for="follow in follows.results"
+        :key="follow.fid"
+      >
         <td><actor-link :actor="follow.actor" /></td>
         <td><human-date :date="follow.creation_date" /></td>
         <td>
-          <span :class="['ui', 'warning', 'basic', 'label']" v-if="follow.approved === null">
+          <span
+            v-if="follow.approved === null"
+            :class="['ui', 'warning', 'basic', 'label']"
+          >
             <translate translate-context="Content/Library/Table/Short">Pending approval</translate>
           </span>
-          <span :class="['ui', 'success', 'basic', 'label']" v-else-if="follow.approved === true">
+          <span
+            v-else-if="follow.approved === true"
+            :class="['ui', 'success', 'basic', 'label']"
+          >
             <translate translate-context="Content/Library/Table/Short">Accepted</translate>
           </span>
-          <span :class="['ui', 'danger', 'basic', 'label']" v-else-if="follow.approved === false">
+          <span
+            v-else-if="follow.approved === false"
+            :class="['ui', 'danger', 'basic', 'label']"
+          >
             <translate translate-context="Content/Library/*/Short">Rejected</translate>
           </span>
         </td>
         <td>
-          <button @click="updateApproved(follow, true)" :class="['ui', 'mini', 'icon', 'labeled', 'success', 'button']" v-if="follow.approved === null || follow.approved === false">
-            <i class="ui check icon"></i> <translate translate-context="Content/Library/Button.Label">Accept</translate>
+          <button
+            v-if="follow.approved === null || follow.approved === false"
+            :class="['ui', 'mini', 'icon', 'labeled', 'success', 'button']"
+            @click="updateApproved(follow, true)"
+          >
+            <i class="ui check icon" /> <translate translate-context="Content/Library/Button.Label">
+              Accept
+            </translate>
           </button>
-          <button @click="updateApproved(follow, false)" :class="['ui', 'mini', 'icon', 'labeled', 'danger', 'button']" v-if="follow.approved === null || follow.approved === true">
-            <i class="ui x icon"></i> <translate translate-context="Content/Library/Button.Label">Reject</translate>
+          <button
+            v-if="follow.approved === null || follow.approved === true"
+            :class="['ui', 'mini', 'icon', 'labeled', 'danger', 'button']"
+            @click="updateApproved(follow, false)"
+          >
+            <i class="ui x icon" /> <translate translate-context="Content/Library/Button.Label">
+              Reject
+            </translate>
           </button>
         </td>
       </tr>
-
     </table>
-    <p v-else><translate translate-context="Content/Library/Paragraph">Nobody is following this library</translate></p>
+    <p v-else>
+      <translate translate-context="Content/Library/Paragraph">
+        Nobody is following this library
+      </translate>
+    </p>
   </section>
 </template>
 
 <script>
-import LibraryFilesTable from "@/views/content/libraries/FilesTable"
-import LibraryForm from "@/views/content/libraries/Form"
-import axios from "axios"
+import LibraryFilesTable from '@/views/content/libraries/FilesTable'
+import LibraryForm from '@/views/content/libraries/Form'
+import axios from 'axios'
 
 export default {
-  props: ['object'],
   components: {
     LibraryForm,
     LibraryFilesTable
   },
+  props: { object: { type: String, required: true } },
   data () {
     return {
       isLoadingFollows: false,
       follows: null
     }
   },
-  created() {
+  created () {
     this.fetchFollows()
   },
   methods: {
-    fetchFollows() {
-      let self = this
+    fetchFollows () {
+      const self = this
       self.isLoadingLibrary = true
       axios.get(`libraries/${this.object.uuid}/follows/`).then(response => {
         self.follows = response.data
         self.isLoadingFollows = false
       })
     },
-    updateApproved(follow, value) {
-      let self = this
+    updateApproved (follow, value) {
       let action
       if (value) {
-        action = "accept"
+        action = 'accept'
       } else {
-        action = "reject"
+        action = 'reject'
       }
       axios
         .post(`federation/follows/library/${follow.uuid}/${action}/`)
diff --git a/front/src/views/library/Upload.vue b/front/src/views/library/Upload.vue
index 66442c43a..48dda7e29 100644
--- a/front/src/views/library/Upload.vue
+++ b/front/src/views/library/Upload.vue
@@ -1,10 +1,11 @@
 <template>
   <section>
-    <file-upload ref="fileupload"
+    <file-upload
+      ref="fileupload"
       :default-import-reference="defaultImportReference"
       :library="object"
-      @uploads-finished="$emit('uploads-finished', $event)" />
-
+      @uploads-finished="$emit('uploads-finished', $event)"
+    />
   </section>
 </template>
 
@@ -13,23 +14,25 @@
 import FileUpload from '@/components/library/FileUpload'
 
 export default {
-  props: ['object', 'defaultImportReference'],
   components: {
-    FileUpload,
+    FileUpload
   },
 
-  beforeRouteLeave (to, from, next){
-    if (this.$refs.fileupload.hasActiveUploads){
+  beforeRouteLeave (to, from, next) {
+    if (this.$refs.fileupload.hasActiveUploads) {
       const answer = window.confirm('This page is asking you to confirm that you want to leave - data you have entered may not be saved.')
       if (answer) {
         next()
       } else {
         next(false)
       }
-    }
-    else{
+    } else {
       next()
     }
+  },
+  props: {
+    object: { type: String, required: true },
+    defaultImportReference: { type: String, required: true }
   }
 }
 </script>
diff --git a/front/src/views/playlists/Detail.vue b/front/src/views/playlists/Detail.vue
index de2a7b3e4..272a0d535 100644
--- a/front/src/views/playlists/Detail.vue
+++ b/front/src/views/playlists/Detail.vue
@@ -1,12 +1,20 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment" v-title="labels.playlist">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      v-title="labels.playlist"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
-    <section v-if="!isLoading && playlist" class="ui head vertical center aligned stripe segment" v-title="playlist.name">
+    <section
+      v-if="!isLoading && playlist"
+      v-title="playlist.name"
+      class="ui head vertical center aligned stripe segment"
+    >
       <div class="segment-content">
         <h2 class="ui center aligned icon header">
-          <i class="circular inverted list warning icon"></i>
+          <i class="circular inverted list warning icon" />
           <div class="content">
             {{ playlist.name }}
             <div class="sub header">
@@ -14,146 +22,213 @@
                 translate-plural="Playlist containing %{ count } tracks, by %{ username }"
                 :translate-n="playlist.tracks_count"
                 :translate-params="{count: playlist.tracks_count, username: playlist.user.username}"
-                translate-context="Content/Playlist/Header.Subtitle">
-              Playlist containing %{ count } track, by %{ username }
+                translate-context="Content/Playlist/Header.Subtitle"
+              >
+                Playlist containing %{ count } track, by %{ username }
               </translate><br>
               <duration :seconds="playlist.duration" />
             </div>
           </div>
         </h2>
-        <div class="ui hidden divider"></div>
+        <div class="ui hidden divider" />
         <div class="header-buttons">
           <div class="ui buttons">
-            <play-button class="vibrant" :is-playable="playlist.is_playable" :tracks="tracks"><translate translate-context="Content/Queue/Button.Label/Short, Verb">Play all</translate></play-button>
+            <play-button
+              class="vibrant"
+              :is-playable="playlist.is_playable"
+              :tracks="tracks"
+            >
+              <translate translate-context="Content/Queue/Button.Label/Short, Verb">
+                Play all
+              </translate>
+            </play-button>
           </div>
           <div class="ui buttons">
             <button
-              class="ui icon labeled button"
               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 translate-context="Content/Playlist/Button.Label/Verb">Stop Editing</translate></template>
-              <template v-else><translate translate-context="Content/*/Button.Label/Verb">Edit</translate></template>
+              class="ui icon labeled button"
+              @click="edit = !edit"
+            >
+              <i class="pencil icon" />
+              <template v-if="edit">
+                <translate translate-context="Content/Playlist/Button.Label/Verb">
+                  Stop Editing
+                </translate>
+              </template>
+              <template v-else>
+                <translate translate-context="Content/*/Button.Label/Verb">
+                  Edit
+                </translate>
+              </template>
             </button>
           </div>
-          <div class="ui buttons">	
+          <div class="ui buttons">
             <button
-              class="ui icon labeled button"
               v-if="playlist.privacy_level === 'everyone' && playlist.is_playable"
-              @click="showEmbedModal = !showEmbedModal">
-              <i class="code icon"></i>
-              <translate translate-context="Content/*/Button.Label/Verb">Embed</translate>
+              class="ui icon labeled button"
+              @click="showEmbedModal = !showEmbedModal"
+            >
+              <i class="code icon" />
+              <translate translate-context="Content/*/Button.Label/Verb">
+                Embed
+              </translate>
             </button>
-            <dangerous-button v-if="$store.state.auth.profile && playlist.user.id === $store.state.auth.profile.id" class="ui labeled danger icon button" :action="deletePlaylist">
-              <i class="trash icon"></i> <translate translate-context="*/*/*/Verb">Delete</translate>
-              <p slot="modal-header" v-translate="{playlist: playlist.name}" translate-context="Popup/Playlist/Title/Call to action" :translate-params="{playlist: playlist.name}">
-              Do you want to delete the playlist "%{ playlist }"?
+            <dangerous-button
+              v-if="$store.state.auth.profile && playlist.user.id === $store.state.auth.profile.id"
+              class="ui labeled danger icon button"
+              :action="deletePlaylist"
+            >
+              <i class="trash icon" /> <translate translate-context="*/*/*/Verb">
+                Delete
+              </translate>
+              <p
+                slot="modal-header"
+                v-translate="{playlist: playlist.name}"
+                translate-context="Popup/Playlist/Title/Call to action"
+                :translate-params="{playlist: playlist.name}"
+              >
+                Do you want to delete the playlist "%{ playlist }"?
               </p>
-              <p slot="modal-content"><translate translate-context="Popup/Playlist/Paragraph">This will completely delete this playlist and cannot be undone.</translate></p>
-              <div slot="modal-confirm"><translate translate-context="Popup/Playlist/Button.Label/Verb">Delete playlist</translate></div>
+              <p slot="modal-content">
+                <translate translate-context="Popup/Playlist/Paragraph">
+                  This will completely delete this playlist and cannot be undone.
+                </translate>
+              </p>
+              <div slot="modal-confirm">
+                <translate translate-context="Popup/Playlist/Button.Label/Verb">
+                  Delete playlist
+                </translate>
+              </div>
             </dangerous-button>
           </div>
         </div>
-        <modal v-if="playlist.privacy_level === 'everyone' && playlist.is_playable" :show.sync="showEmbedModal">
-        <h4 class="header">
-          <translate translate-context="Popup/Album/Title/Verb">Embed this playlist on your website</translate>
-        </h4>
-        <div class="scrolling content">
-          <div class="description">
-            <embed-wizard type="playlist" :id="playlist.id" />
+        <modal
+          v-if="playlist.privacy_level === 'everyone' && playlist.is_playable"
+          :show.sync="showEmbedModal"
+        >
+          <h4 class="header">
+            <translate translate-context="Popup/Album/Title/Verb">
+              Embed this playlist on your website
+            </translate>
+          </h4>
+          <div class="scrolling content">
+            <div class="description">
+              <embed-wizard
+                :id="playlist.id"
+                type="playlist"
+              />
+            </div>
+          </div>
+          <div class="actions">
+            <button class="ui basic deny button">
+              <translate translate-context="*/*/Button.Label/Verb">
+                Cancel
+              </translate>
+            </button>
           </div>
-        </div>
-        <div class="actions">
-          <button class="ui basic deny button">
-            <translate translate-context="*/*/Button.Label/Verb">Cancel</translate>
-          </button>
-        </div>
         </modal>
       </div>
     </section>
     <section class="ui vertical stripe segment">
       <template v-if="edit">
         <playlist-editor
+          :playlist="playlist"
+          :playlist-tracks="playlistTracks"
           @playlist-updated="playlist = $event"
           @tracks-updated="updatePlts"
-          :playlist="playlist" :playlist-tracks="playlistTracks"></playlist-editor>
+        />
       </template>
       <template v-else-if="tracks.length > 0">
-        <h2><translate translate-context="*/*/*">Tracks</translate></h2>
-        <track-table :display-position="true" :tracks="tracks"></track-table>
+        <h2>
+          <translate translate-context="*/*/*">
+            Tracks
+          </translate>
+        </h2>
+        <track-table
+          :display-position="true"
+          :tracks="tracks"
+        />
       </template>
-      <div v-else class="ui placeholder segment">
+      <div
+        v-else
+        class="ui placeholder segment"
+      >
         <div class="ui icon header">
-          <i class="list icon"></i>
-          <translate translate-context="Content/Home/Placeholder">There are no tracks in this playlist yet</translate>
+          <i class="list icon" />
+          <translate translate-context="Content/Home/Placeholder">
+            There are no tracks in this playlist yet
+          </translate>
         </div>
-        <button @click="edit = !edit" class="ui success icon labeled button">
-          <i class="pencil icon"></i>
-          <translate translate-context="Content/Home/CreatePlaylist">Edit</translate>
+        <button
+          class="ui success icon labeled button"
+          @click="edit = !edit"
+        >
+          <i class="pencil icon" />
+          <translate translate-context="Content/Home/CreatePlaylist">
+            Edit
+          </translate>
         </button>
       </div>
     </section>
   </main>
 </template>
 <script>
-import axios from "axios"
-import TrackTable from "@/components/audio/track/Table"
-import RadioButton from "@/components/radios/Button"
-import PlayButton from "@/components/audio/PlayButton"
-import PlaylistEditor from "@/components/playlists/Editor"
-import EmbedWizard from "@/components/audio/EmbedWizard"
+import axios from 'axios'
+import TrackTable from '@/components/audio/track/Table'
+import PlayButton from '@/components/audio/PlayButton'
+import PlaylistEditor from '@/components/playlists/Editor'
+import EmbedWizard from '@/components/audio/EmbedWizard'
 import Modal from '@/components/semantic/Modal'
 
 export default {
-  props: {
-    id: { required: true },
-    defaultEdit: { type: Boolean, default: false }
-  },
   components: {
     PlaylistEditor,
     TrackTable,
     PlayButton,
-    RadioButton,
     Modal,
-    EmbedWizard,
+    EmbedWizard
   },
-  data: function() {
+  props: {
+    id: { type: Number, required: true },
+    defaultEdit: { type: Boolean, default: false }
+  },
+  data: function () {
     return {
       edit: this.defaultEdit,
       isLoading: false,
       playlist: null,
       tracks: [],
       playlistTracks: [],
-      showEmbedModal: false,
+      showEmbedModal: false
     }
   },
-  created: function() {
-    this.fetch()
-  },
   computed: {
-    labels() {
+    labels () {
       return {
         playlist: this.$pgettext('*/*/*', 'Playlist')
       }
     }
   },
+  created: function () {
+    this.fetch()
+  },
   methods: {
-    updatePlts(v) {
+    updatePlts (v) {
       this.playlistTracks = v
       this.tracks = v.map((e, i) => {
-        let track = e.track
+        const track = e.track
         track.position = i + 1
         return track
       })
     },
-    fetch: function() {
-      let self = this
+    fetch: function () {
+      const self = this
       self.isLoading = true
-      let url = "playlists/" + this.id + "/"
+      const url = 'playlists/' + this.id + '/'
       axios.get(url).then(response => {
         self.playlist = response.data
         axios
-          .get(url + "tracks/")
+          .get(url + 'tracks/')
           .then(response => {
             self.updatePlts(response.data.results)
           })
@@ -162,13 +237,13 @@ export default {
           })
       })
     },
-    deletePlaylist() {
-      let self = this
-      let url = "playlists/" + this.id + "/"
+    deletePlaylist () {
+      const self = this
+      const url = 'playlists/' + this.id + '/'
       axios.delete(url).then(response => {
-        self.$store.dispatch("playlists/fetchOwn")
+        self.$store.dispatch('playlists/fetchOwn')
         self.$router.push({
-          path: "/library"
+          path: '/library'
         })
       })
     }
diff --git a/front/src/views/playlists/List.vue b/front/src/views/playlists/List.vue
index ddfabd6fb..4ad0cdb12 100644
--- a/front/src/views/playlists/List.vue
+++ b/front/src/views/playlists/List.vue
@@ -1,152 +1,217 @@
 <template>
   <main v-title="labels.playlists">
     <section class="ui vertical stripe segment">
-      <h2 class="ui header"><translate translate-context="Content/Playlist/Title">Browsing playlists</translate></h2>
+      <h2 class="ui header">
+        <translate translate-context="Content/Playlist/Title">
+          Browsing playlists
+        </translate>
+      </h2>
       <template v-if="$store.state.auth.authenticated">
         <button
+          class="ui success button"
           @click="$store.commit('playlists/chooseTrack', null)"
-          class="ui success button"><translate translate-context="Content/Playlist/Button.Label/Verb">Manage your playlists</translate></button>
-        <div class="ui hidden divider"></div>
+        >
+          <translate translate-context="Content/Playlist/Button.Label/Verb">
+            Manage your playlists
+          </translate>
+        </button>
+        <div class="ui hidden divider" />
       </template>
-      <form :class="['ui', {'loading': isLoading}, 'form']" @submit.prevent="updateQueryString();fetchData()">
+      <form
+        :class="['ui', {'loading': isLoading}, 'form']"
+        @submit.prevent="updateQueryString();fetchData()"
+      >
         <div class="fields">
           <div class="field">
             <label for="playlists-search"><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label>
             <div class="ui action input">
-              <input id="playlists-search" stype="text" name="search" v-model="query" :placeholder="labels.searchPlaceholder"/>
-              <button class="ui icon button" type="submit" :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')">
-                <i class="search icon"></i>
+              <input
+                id="playlists-search"
+                v-model="query"
+                stype="text"
+                name="search"
+                :placeholder="labels.searchPlaceholder"
+              >
+              <button
+                class="ui icon button"
+                type="submit"
+                :aria-label="$pgettext('Content/Search/Input.Label/Noun', 'Search')"
+              >
+                <i class="search icon" />
               </button>
             </div>
           </div>
           <div class="field">
             <label for="playlists-ordering"><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label>
-            <select id="playlists-ordering" class="ui dropdown" v-model="ordering">
-              <option v-for="option in orderingOptions" :value="option[0]">
+            <select
+              id="playlists-ordering"
+              v-model="ordering"
+              class="ui dropdown"
+            >
+              <option
+                v-for="option in orderingOptions"
+                :key="option[0]"
+                :value="option[0]"
+              >
                 {{ sharedLabels.filters[option[1]] }}
               </option>
             </select>
           </div>
           <div class="field">
             <label for="playlists-ordering-direction"><translate translate-context="Content/Search/Dropdown.Label/Noun">Order</translate></label>
-            <select id="playlists-ordering-direction" class="ui dropdown" v-model="orderingDirection">
-              <option value="+"><translate translate-context="Content/Search/Dropdown">Ascending</translate></option>
-              <option value="-"><translate translate-context="Content/Search/Dropdown">Descending</translate></option>
+            <select
+              id="playlists-ordering-direction"
+              v-model="orderingDirection"
+              class="ui dropdown"
+            >
+              <option value="+">
+                <translate translate-context="Content/Search/Dropdown">
+                  Ascending
+                </translate>
+              </option>
+              <option value="-">
+                <translate translate-context="Content/Search/Dropdown">
+                  Descending
+                </translate>
+              </option>
             </select>
           </div>
           <div class="field">
             <label for="playlists-results"><translate translate-context="Content/Search/Dropdown.Label/Noun">Results per page</translate></label>
-            <select id="playlists-results" class="ui dropdown" v-model="paginateBy">
-              <option :value="parseInt(12)">12</option>
-              <option :value="parseInt(25)">25</option>
-              <option :value="parseInt(50)">50</option>
+            <select
+              id="playlists-results"
+              v-model="paginateBy"
+              class="ui dropdown"
+            >
+              <option :value="parseInt(12)">
+                12
+              </option>
+              <option :value="parseInt(25)">
+                25
+              </option>
+              <option :value="parseInt(50)">
+                50
+              </option>
             </select>
           </div>
         </div>
       </form>
-      <div class="ui hidden divider"></div>
-      <playlist-card-list v-if="result && result.results.length > 0" :playlists="result.results"></playlist-card-list>
-      <div v-else-if="result && !result.results.length > 0" class="ui placeholder segment sixteen wide column" style="text-align: center; display: flex; align-items: center">
+      <div class="ui hidden divider" />
+      <playlist-card-list
+        v-if="result && result.results.length > 0"
+        :playlists="result.results"
+      />
+      <div
+        v-else-if="result && !result.results.length > 0"
+        class="ui placeholder segment sixteen wide column"
+        style="text-align: center; display: flex; align-items: center"
+      >
         <div class="ui icon header">
-          <i class="list icon"></i>
+          <i class="list icon" />
           <translate translate-context="Content/Playlists/Placeholder">
             No results matching your query
           </translate>
         </div>
         <button
-        v-if="$store.state.auth.authenticated"
-        @click="$store.commit('playlists/chooseTrack', null)"
-        class="ui success button labeled icon">
-        <i class="list icon"></i>
-        <translate translate-context="Content/*/Verb">
-          Create a playlist
+          v-if="$store.state.auth.authenticated"
+          class="ui success button labeled icon"
+          @click="$store.commit('playlists/chooseTrack', null)"
+        >
+          <i class="list icon" />
+          <translate translate-context="Content/*/Verb">
+            Create a playlist
           </translate>
         </button>
       </div>
       <div class="ui center aligned basic segment">
         <pagination
           v-if="result && result.results.length > 0"
-          @page-changed="selectPage"
           :current="page"
           :paginate-by="paginateBy"
           :total="result.count"
-          ></pagination>
+          @page-changed="selectPage"
+        />
       </div>
     </section>
   </main>
 </template>
 
 <script>
-import axios from "axios"
-import _ from "@/lodash"
-import $ from "jquery"
+import axios from 'axios'
+import $ from 'jquery'
 
-import OrderingMixin from "@/components/mixins/Ordering"
-import PaginationMixin from "@/components/mixins/Pagination"
-import TranslationsMixin from "@/components/mixins/Translations"
-import PlaylistCardList from "@/components/playlists/CardList"
-import Pagination from "@/components/Pagination"
+import OrderingMixin from '@/components/mixins/Ordering'
+import PaginationMixin from '@/components/mixins/Pagination'
+import TranslationsMixin from '@/components/mixins/Translations'
+import PlaylistCardList from '@/components/playlists/CardList'
+import Pagination from '@/components/Pagination'
 
-const FETCH_URL = "playlists/"
+const FETCH_URL = 'playlists/'
 
 export default {
-  mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
-  props: {
-    defaultQuery: { type: String, required: false, default: "" },
-    scope: { type: String, required: false, default: "all" },
-  },
   components: {
     PlaylistCardList,
     Pagination
   },
-  data() {
+  mixins: [OrderingMixin, PaginationMixin, TranslationsMixin],
+  props: {
+    defaultQuery: { type: String, required: false, default: '' },
+    scope: { type: String, required: false, default: 'all' }
+  },
+  data () {
     return {
       isLoading: true,
       result: null,
       page: parseInt(this.defaultPage),
       query: this.defaultQuery,
       orderingOptions: [
-        ["creation_date", "creation_date"],
-        ["modification_date", "modification_date"],
-        ["name", "name"]
+        ['creation_date', 'creation_date'],
+        ['modification_date', 'modification_date'],
+        ['name', 'name']
       ]
     }
   },
-  created() {
-    this.fetchData()
-  },
-  mounted() {
-    $(".ui.dropdown").dropdown()
-  },
   computed: {
-    labels() {
-      let playlists = this.$pgettext('*/*/*', 'Playlists')
-      let searchPlaceholder = this.$pgettext('Content/Playlist/Placeholder/Call to action', 'Enter playlist name…')
+    labels () {
+      const playlists = this.$pgettext('*/*/*', 'Playlists')
+      const searchPlaceholder = this.$pgettext('Content/Playlist/Placeholder/Call to action', 'Enter playlist name…')
       return {
         playlists,
         searchPlaceholder
       }
     }
   },
+  watch: {
+    page () {
+      this.updateQueryString()
+      this.fetchData()
+    }
+  },
+  created () {
+    this.fetchData()
+  },
+  mounted () {
+    $('.ui.dropdown').dropdown()
+  },
   methods: {
-    updateQueryString: function() {
+    updateQueryString: function () {
       history.pushState(
         {},
         null,
         this.$route.path + '?' + new URLSearchParams(
           {
-          query: this.query,
-          page: this.page,
-          paginateBy: this.paginateBy,
-          ordering: this.getOrderingAsString()
-        }).toString()
+            query: this.query,
+            page: this.page,
+            paginateBy: this.paginateBy,
+            ordering: this.getOrderingAsString()
+          }).toString()
       )
     },
-    fetchData: function() {
-      var self = this
+    fetchData: function () {
+      const self = this
       this.isLoading = true
-      let url = FETCH_URL
-      let params = {
+      const url = FETCH_URL
+      const params = {
         scope: this.scope,
         page: this.page,
         page_size: this.paginateBy,
@@ -159,15 +224,9 @@ export default {
         self.isLoading = false
       })
     },
-    selectPage: function(page) {
+    selectPage: function (page) {
       this.page = page
     }
-  },
-  watch: {
-    page() {
-      this.updateQueryString()
-      this.fetchData()
-    },
   }
 }
 </script>
diff --git a/front/src/views/radios/Detail.vue b/front/src/views/radios/Detail.vue
index 37c2b9acc..7c72ff87b 100644
--- a/front/src/views/radios/Detail.vue
+++ b/front/src/views/radios/Detail.vue
@@ -1,61 +1,108 @@
 <template>
   <main>
-    <div v-if="isLoading" class="ui vertical segment" v-title="labels.title">
-      <div :class="['ui', 'centered', 'active', 'inline', 'loader']"></div>
+    <div
+      v-if="isLoading"
+      v-title="labels.title"
+      class="ui vertical segment"
+    >
+      <div :class="['ui', 'centered', 'active', 'inline', 'loader']" />
     </div>
-    <section v-if="!isLoading && radio" class="ui head vertical center aligned stripe segment" v-title="radio.name">
+    <section
+      v-if="!isLoading && radio"
+      v-title="radio.name"
+      class="ui head vertical center aligned stripe segment"
+    >
       <div class="segment-content">
         <h2 class="ui center aligned icon header">
-          <i class="circular inverted feed primary icon"></i>
+          <i class="circular inverted feed primary icon" />
           <div class="content">
             {{ radio.name }}
             <div class="sub header">
               Radio containing {{ totalTracks }} tracks,
-              by <username :username="radio.user.username"></username>
+              by <username :username="radio.user.username" />
             </div>
           </div>
         </h2>
-        <div class="ui hidden divider"></div>
-        <radio-button type="custom" :custom-radio-id="radio.id"></radio-button>
+        <div class="ui hidden divider" />
+        <radio-button
+          type="custom"
+          :custom-radio-id="radio.id"
+        />
         <template v-if="$store.state.auth.username === radio.user.username">
-          <router-link class="ui icon labeled button" :to="{name: 'library.radios.edit', params: {id: radio.id}}" exact>
-            <i class="pencil icon"></i>
+          <router-link
+            class="ui icon labeled button"
+            :to="{name: 'library.radios.edit', params: {id: radio.id}}"
+            exact
+          >
+            <i class="pencil icon" />
             Edit…
           </router-link>
-          <dangerous-button class="ui labeled danger icon button" :action="deleteRadio">
-            <i class="trash icon"></i> Delete
-            <p slot="modal-header" v-translate="{radio: radio.name}"  translate-context="Popup/Radio/Title" :translate-params="{radio: radio.name}">Do you want to delete the radio "%{ radio }"?</p>
-            <p slot="modal-content"><translate translate-context="Popup/Radio/Paragraph">This will completely delete this radio and cannot be undone.</translate></p>
-            <p slot="modal-confirm"><translate translate-context="Popup/Radio/Button.Label/Verb">Delete radio</translate></p>
+          <dangerous-button
+            class="ui labeled danger icon button"
+            :action="deleteRadio"
+          >
+            <i class="trash icon" /> Delete
+            <p
+              slot="modal-header"
+              v-translate="{radio: radio.name}"
+              translate-context="Popup/Radio/Title"
+              :translate-params="{radio: radio.name}"
+            >
+              Do you want to delete the radio "%{ radio }"?
+            </p>
+            <p slot="modal-content">
+              <translate translate-context="Popup/Radio/Paragraph">
+                This will completely delete this radio and cannot be undone.
+              </translate>
+            </p>
+            <p slot="modal-confirm">
+              <translate translate-context="Popup/Radio/Button.Label/Verb">
+                Delete radio
+              </translate>
+            </p>
           </dangerous-button>
         </template>
       </div>
     </section>
-    <section v-if="totalTracks > 0" class="ui vertical stripe segment">
-      <h2><translate translate-context="*/*/*">Tracks</translate></h2>
-      <track-table :tracks="tracks"></track-table>
+    <section
+      v-if="totalTracks > 0"
+      class="ui vertical stripe segment"
+    >
+      <h2>
+        <translate translate-context="*/*/*">
+          Tracks
+        </translate>
+      </h2>
+      <track-table :tracks="tracks" />
       <div class="ui center aligned basic segment">
         <pagination
           v-if="totalTracks > 25"
-          @page-changed="selectPage"
           :current="page"
           :paginate-by="25"
           :total="totalTracks"
-          ></pagination>
+          @page-changed="selectPage"
+        />
       </div>
     </section>
-    <div v-else-if="!isLoading && !totalTracks > 0" class="ui placeholder segment">
+    <div
+      v-else-if="!isLoading && !totalTracks > 0"
+      class="ui placeholder segment"
+    >
       <div class="ui icon header">
-        <i class="rss icon"></i>
+        <i class="rss icon" />
         <translate
-        translate-context="Content/Radios/Placeholder"
-        >No tracks have been added to this radio yet</translate>
+          translate-context="Content/Radios/Placeholder"
+        >
+          No tracks have been added to this radio yet
+        </translate>
       </div>
       <router-link
-      v-if="$store.state.auth.username === radio.user.username"
-      class="ui success icon labeled button"
-      :to="{name: 'library.radios.edit', params: {id: radio.id}}" exact>
-      <i class="pencil icon"></i>
+        v-if="$store.state.auth.username === radio.user.username"
+        class="ui success icon labeled button"
+        :to="{name: 'library.radios.edit', params: {id: radio.id}}"
+        exact
+      >
+        <i class="pencil icon" />
         Edit…
       </router-link>
     </div>
@@ -63,21 +110,21 @@
 </template>
 
 <script>
-import axios from "axios"
-import TrackTable from "@/components/audio/track/Table"
-import RadioButton from "@/components/radios/Button"
-import Pagination from "@/components/Pagination"
+import axios from 'axios'
+import TrackTable from '@/components/audio/track/Table'
+import RadioButton from '@/components/radios/Button'
+import Pagination from '@/components/Pagination'
 
 export default {
-  props: {
-    id: { required: true }
-  },
   components: {
     TrackTable,
     RadioButton,
     Pagination
   },
-  data: function() {
+  props: {
+    id: { type: Number, required: true }
+  },
+  data: function () {
     return {
       isLoading: false,
       radio: null,
@@ -86,28 +133,33 @@ export default {
       page: 1
     }
   },
-  created: function() {
-    this.fetch()
-  },
   computed: {
-    labels() {
+    labels () {
       return {
-        title: this.$pgettext('Head/Radio/Title', "Radio")
+        title: this.$pgettext('Head/Radio/Title', 'Radio')
       }
     }
   },
+  watch: {
+    page: function () {
+      this.fetch()
+    }
+  },
+  created: function () {
+    this.fetch()
+  },
   methods: {
-    selectPage: function(page) {
+    selectPage: function (page) {
       this.page = page
     },
-    fetch: function() {
-      let self = this
+    fetch: function () {
+      const self = this
       self.isLoading = true
-      let url = "radios/radios/" + this.id + "/"
+      const url = 'radios/radios/' + this.id + '/'
       axios.get(url).then(response => {
         self.radio = response.data
         axios
-          .get(url + "tracks/", { params: { page: this.page } })
+          .get(url + 'tracks/', { params: { page: this.page } })
           .then(response => {
             this.totalTracks = response.data.count
             this.tracks = response.data.results
@@ -117,20 +169,15 @@ export default {
           })
       })
     },
-    deleteRadio() {
-      let self = this
-      let url = "radios/radios/" + this.id + "/"
+    deleteRadio () {
+      const self = this
+      const url = 'radios/radios/' + this.id + '/'
       axios.delete(url).then(response => {
         self.$router.push({
-          path: "/library"
+          path: '/library'
         })
       })
     }
-  },
-  watch: {
-    page: function() {
-      this.fetch()
-    }
   }
 }
 </script>
-- 
GitLab