From b41f22db3d7d2fd376c915bf951feedce70c15c6 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Tue, 1 Nov 2022 17:33:52 +0100
Subject: [PATCH] Reformat and fix lint

(cherry picked from commit 30f3fe2c5362d785c8d93366b52e3983f247c500)
---
 .gitlab-ci.yml                                |  40 +-
 aleksis/core/assets/app.js                    | 188 ++++-----
 .../assets/components/CacheNotification.vue   |  36 +-
 .../core/assets/components/LanguageForm.vue   |  42 +-
 aleksis/core/assets/components/MessageBox.vue |  20 +-
 .../core/assets/components/SidenavSearch.vue  |  37 +-
 .../notifications/NotificationItem.vue        |  19 +-
 .../notifications/NotificationList.vue        |  20 +-
 aleksis/core/assets/css/global.scss           |   9 +-
 aleksis/core/assets/index.js                  |   6 +-
 aleksis/core/assets/messages.json             |   3 +-
 aleksis/core/assets/util.js                   | 167 +-------
 aleksis/core/static/js/copy_button.js         |  26 +-
 aleksis/core/static/js/edit_dashboard.js      |  34 +-
 aleksis/core/static/js/helper.js              |  33 +-
 aleksis/core/static/js/include_ajax_live.js   |   8 +-
 aleksis/core/static/js/main.js                | 364 +++++++++---------
 aleksis/core/static/js/multi_select.js        |  70 ++--
 aleksis/core/static/js/progress.js            | 124 +++---
 aleksis/core/static/js/search.js              | 222 +++++------
 aleksis/core/static/js/serviceworker.js       | 114 +++---
 aleksis/core/static/print-simple.css          |  24 +-
 aleksis/core/static/print.css                 | 154 ++++----
 aleksis/core/static/print_landscape.css       |  17 +-
 aleksis/core/static/public/style.scss         | 123 +++---
 aleksis/core/static/public/theme.scss         | 113 +++---
 aleksis/core/templates/core/vue_base.html     |   8 -
 .../core/templates/templated_email/email.css  |  56 +--
 aleksis/core/webpack.config.js                |  62 +--
 29 files changed, 1059 insertions(+), 1080 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 564449be1..cf02f39e3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,21 +1,21 @@
 include:
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/general.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/prepare/lock.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/test/test.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/test/lint.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/test/security.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/build/dist.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/publish/pypi.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: /ci/docker/image.yml
-    - project: "AlekSIS/official/AlekSIS"
-      file: "/ci/deploy/review.yml"
-    - project: "AlekSIS/official/AlekSIS"
-      file: "/ci/deploy/trigger_dist.yml"
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/general.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/prepare/lock.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/test/test.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/test/lint.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/test/security.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/build/dist.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/publish/pypi.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: /ci/docker/image.yml
+  - project: "AlekSIS/official/AlekSIS"
+    file: "/ci/deploy/review.yml"
+  - project: "AlekSIS/official/AlekSIS"
+    file: "/ci/deploy/trigger_dist.yml"
diff --git a/aleksis/core/assets/app.js b/aleksis/core/assets/app.js
index 32d4f1b0e..148612e29 100644
--- a/aleksis/core/assets/app.js
+++ b/aleksis/core/assets/app.js
@@ -1,74 +1,82 @@
-import Vue from "vue"
-import VueRouter from "vue-router"
-import Vuetify from "vuetify"
-import "vuetify/dist/vuetify.min.css"
+import Vue from "vue";
+import VueRouter from "vue-router";
+import Vuetify from "vuetify";
+import "vuetify/dist/vuetify.min.css";
 
-import ApolloClient from 'apollo-boost'
-import VueApollo from 'vue-apollo'
-import gql from 'graphql-tag'
+import ApolloClient from "apollo-boost";
+import VueApollo from "vue-apollo";
 
-import "./css/global.scss"
-import VueI18n from 'vue-i18n'
+import "./css/global.scss";
+import VueI18n from "vue-i18n";
 
-import messages from "./messages.json"
+import messages from "./messages.json";
 
-Vue.use(VueI18n)
+Vue.use(VueI18n);
 
 const i18n = new VueI18n({
-    locale: "en",
-    fallbackLocale: "en",
-    messages
+  locale: "en",
+  fallbackLocale: "en",
+  messages,
 });
 
 // Using this function, apps can register their locale files
 i18n.registerLocale = function (messages) {
-    for (let locale in messages) {
-        i18n.mergeLocaleMessage(locale, messages[locale]);
-    }
+  for (let locale in messages) {
+    i18n.mergeLocaleMessage(locale, messages[locale]);
+  }
 };
 
-Vue.use(Vuetify)
-Vue.use(VueRouter)
+Vue.use(Vuetify);
+Vue.use(VueRouter);
 
 const vuetify = new Vuetify({
-    // TODO: load theme data dynamically
-    //  - find a way to load template context data
-    //  - include all site preferences
-    //  - load menu stuff to render the sidenav
-    icons: {
-        iconfont: 'mdi', // default - only for display purposes
-        values: {
-          cancel: 'mdi-close-circle-outline',
-          delete: 'mdi-close-circle-outline',
-          success: 'mdi-check-circle-outline',
-          info: 'mdi-information-outline',
-          warning: 'mdi-alert-outline',
-          error: 'mdi-alert-octagon-outline',
-          prev: 'mdi-chevron-left',
-          next: 'mdi-chevron-right',
-          checkboxOn: 'mdi-checkbox-marked-outline',
-          checkboxIndeterminate: 'mdi-minus-box-outline',
-          edit: 'mdi-pencil-outline',
-        },
+  // TODO: load theme data dynamically
+  //  - find a way to load template context data
+  //  - include all site preferences
+  //  - load menu stuff to render the sidenav
+  icons: {
+    iconfont: "mdi", // default - only for display purposes
+    values: {
+      cancel: "mdi-close-circle-outline",
+      delete: "mdi-close-circle-outline",
+      success: "mdi-check-circle-outline",
+      info: "mdi-information-outline",
+      warning: "mdi-alert-outline",
+      error: "mdi-alert-octagon-outline",
+      prev: "mdi-chevron-left",
+      next: "mdi-chevron-right",
+      checkboxOn: "mdi-checkbox-marked-outline",
+      checkboxIndeterminate: "mdi-minus-box-outline",
+      edit: "mdi-pencil-outline",
     },
-    theme: {
-        dark: JSON.parse(document.getElementById("design-mode").textContent) === "dark",
-        themes: {
-            light: {
-                primary: JSON.parse(document.getElementById("primary-color").textContent),
-                secondary: JSON.parse(document.getElementById("secondary-color").textContent),
-            },
-            dark: {
-                primary: JSON.parse(document.getElementById("primary-color").textContent),
-                secondary: JSON.parse(document.getElementById("secondary-color").textContent),
-            },
-        },
+  },
+  theme: {
+    dark:
+      JSON.parse(document.getElementById("design-mode").textContent) === "dark",
+    themes: {
+      light: {
+        primary: JSON.parse(
+          document.getElementById("primary-color").textContent
+        ),
+        secondary: JSON.parse(
+          document.getElementById("secondary-color").textContent
+        ),
+      },
+      dark: {
+        primary: JSON.parse(
+          document.getElementById("primary-color").textContent
+        ),
+        secondary: JSON.parse(
+          document.getElementById("secondary-color").textContent
+        ),
+      },
     },
-})
+  },
+});
 
 const apolloClient = new ApolloClient({
-  uri: JSON.parse(document.getElementById("graphql-url").textContent)
-})
+  uri: JSON.parse(document.getElementById("graphql-url").textContent),
+});
 
 import CacheNotification from "./components/CacheNotification.vue";
 import LanguageForm from "./components/LanguageForm.vue";
@@ -78,54 +86,54 @@ import SidenavSearch from "./components/SidenavSearch.vue";
 
 Vue.component(MessageBox.name, MessageBox); // Load MessageBox globally as other components depend on it
 
-Vue.use(VueApollo)
+Vue.use(VueApollo);
 
 const apolloProvider = new VueApollo({
   defaultClient: apolloClient,
-})
+});
 
 const router = new VueRouter({
   mode: "history",
-//  routes: [
-//    { path: "/", component: "TheApp" },
-//  }
+  //  routes: [
+  //    { path: "/", component: "TheApp" },
+  //  }
 });
 
 const app = new Vue({
-    el: '#app',
-    apolloProvider,
-    vuetify: vuetify,
-    // delimiters: ["<%","%>"] // FIXME: discuss new delimiters, [[ <% [{ {[ <[ (( …
-    data: () => ({
-        drawer: vuetify.framework.breakpoint.lgAndUp,
-        group: null, // what does this mean?
-        urls: window.Urls,
-        django: window.django,
-        // FIXME: maybe just use window.django in every component or find a suitable way to access this property everywhere
-        showCacheAlert: false,
-        systemProperties: {
-            currentLanguage: "en",
-            availableLanguages: [],
-        },
-    }),
-    apollo: {
-        systemProperties: require("./systemProperties.graphql"),
+  el: "#app",
+  apolloProvider,
+  vuetify: vuetify,
+  // delimiters: ["<%","%>"] // FIXME: discuss new delimiters, [[ <% [{ {[ <[ (( …
+  data: () => ({
+    drawer: vuetify.framework.breakpoint.lgAndUp,
+    group: null, // what does this mean?
+    urls: window.Urls,
+    django: window.django,
+    // FIXME: maybe just use window.django in every component or find a suitable way to access this property everywhere
+    showCacheAlert: false,
+    systemProperties: {
+      currentLanguage: "en",
+      availableLanguages: [],
     },
-    watch: {
-        systemProperties: function (newProperties) {
-            this.$i18n.locale = newProperties.currentLanguage;
-            this.$vuetify.lang.current = newProperties.currentLanguage;
-        }
+  }),
+  apollo: {
+    systemProperties: require("./systemProperties.graphql"),
+  },
+  watch: {
+    systemProperties: function (newProperties) {
+      this.$i18n.locale = newProperties.currentLanguage;
+      this.$vuetify.lang.current = newProperties.currentLanguage;
     },
-    components: {
-        "cache-notification": CacheNotification,
-        "language-form": LanguageForm,
-        "notification-list": NotificationList,
-        "sidenav-search": SidenavSearch,
-    },
-    router,
-    i18n
-})
+  },
+  components: {
+    "cache-notification": CacheNotification,
+    "language-form": LanguageForm,
+    "notification-list": NotificationList,
+    "sidenav-search": SidenavSearch,
+  },
+  router,
+  i18n,
+});
 
 window.app = app;
 window.router = router;
diff --git a/aleksis/core/assets/components/CacheNotification.vue b/aleksis/core/assets/components/CacheNotification.vue
index d636a056c..f30adbd0b 100644
--- a/aleksis/core/assets/components/CacheNotification.vue
+++ b/aleksis/core/assets/components/CacheNotification.vue
@@ -1,25 +1,25 @@
 <template>
   <message-box :value="cache" type="warning">
-    {{ $t('alerts.page_cached') }}
+    {{ $t("alerts.page_cached") }}
   </message-box>
 </template>
 
 <script>
-  export default {
-    name: "cache-notification",
-    data () {
-        return {
-            cache: false,
-        }
-    },
-    created() {
-        this.channel = new BroadcastChannel("cache-or-not");
-        this.channel.onmessage = (event) => {
-            this.cache = event.data === true;
-        }
-    },
-      destroyed(){
-        this.channel.close()
-      },
-  }
+export default {
+  name: "CacheNotification",
+  data() {
+    return {
+      cache: false,
+    };
+  },
+  created() {
+    this.channel = new BroadcastChannel("cache-or-not");
+    this.channel.onmessage = (event) => {
+      this.cache = event.data === true;
+    };
+  },
+  destroyed() {
+    this.channel.close();
+  },
+};
 </script>
diff --git a/aleksis/core/assets/components/LanguageForm.vue b/aleksis/core/assets/components/LanguageForm.vue
index 2897d3a30..d9b0d0b70 100644
--- a/aleksis/core/assets/components/LanguageForm.vue
+++ b/aleksis/core/assets/components/LanguageForm.vue
@@ -1,31 +1,31 @@
 <template>
   <v-menu offset-y>
-    <template v-slot:activator="{ on, attrs }">
-      <v-btn
-          depressed
-          v-bind="attrs"
-          v-on="on"
-          color="primary"
-      >
+    <template #activator="{ on, attrs }">
+      <v-btn depressed v-bind="attrs" v-on="on" color="primary">
         <v-icon icon color="white">mdi-translate</v-icon>
         {{ $i18n.locale }}
       </v-btn>
     </template>
     <v-list id="language-dropdown" class="dropdown-content" min-width="150">
       <v-skeleton-loader
-          v-if="!$root.systemProperties.availableLanguages"
-          class="mx-auto"
-          type="list-item, list-item, list-item"
+        v-if="!$root.systemProperties.availableLanguages"
+        class="mx-auto"
+        type="list-item, list-item, list-item"
       ></v-skeleton-loader>
       <v-list-item-group
-          v-if="$root.systemProperties.availableLanguages"
-          v-model="$i18n.locale"
-          color="primary"
+        v-if="$root.systemProperties.availableLanguages"
+        v-model="$i18n.locale"
+        color="primary"
       >
-        <v-list-item v-for="languageOption in $root.systemProperties.availableLanguages" :key="languageOption.code"
-                     :value="languageOption.code"
-                     @click="setLanguage(languageOption)">
-          <v-list-item-title>{{ languageOption.nameTranslated }}</v-list-item-title>
+        <v-list-item
+          v-for="languageOption in $root.systemProperties.availableLanguages"
+          :key="languageOption.code"
+          :value="languageOption.code"
+          @click="setLanguage(languageOption)"
+        >
+          <v-list-item-title>{{
+            languageOption.nameTranslated
+          }}</v-list-item-title>
         </v-list-item>
       </v-list-item-group>
     </v-list>
@@ -36,8 +36,8 @@
 export default {
   data: function () {
     return {
-      language: this.$i18n.locale
-    }
+      language: this.$i18n.locale,
+    };
   },
   methods: {
     setLanguage: function (languageOption) {
@@ -46,6 +46,6 @@ export default {
       this.$vuetify.lang.current = languageOption.code;
     },
   },
-  name: "language-form",
-}
+  name: "LanguageForm",
+};
 </script>
diff --git a/aleksis/core/assets/components/MessageBox.vue b/aleksis/core/assets/components/MessageBox.vue
index 2a4cb1729..4c79f4d51 100644
--- a/aleksis/core/assets/components/MessageBox.vue
+++ b/aleksis/core/assets/components/MessageBox.vue
@@ -1,15 +1,15 @@
 <script>
-  export default {
-    name: "message-box",
-    // Due to this component being a wrapper to a v-alert, all props of this can be used (and overridden).
-  }
+export default {
+  name: "MessageBox",
+  // Due to this component being a wrapper to a v-alert, all props of this can be used (and overridden).
+};
 </script>
 
 <template>
-    <v-alert border="left" text v-bind="$attrs">
-      <slot>
-        Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
-      </slot>
-    </v-alert>
+  <v-alert border="left" text v-bind="$attrs">
+    <slot>
+      Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+      tempor incididunt ut labore et dolore magna aliqua.
+    </slot>
+  </v-alert>
 </template>
-
diff --git a/aleksis/core/assets/components/SidenavSearch.vue b/aleksis/core/assets/components/SidenavSearch.vue
index 30fdbe959..1c94ce9ef 100644
--- a/aleksis/core/assets/components/SidenavSearch.vue
+++ b/aleksis/core/assets/components/SidenavSearch.vue
@@ -1,21 +1,36 @@
 <script>
-  export default {
-    methods: {
-        submit: function () {
-            this.$refs.form.submit()
-        },
+export default {
+  methods: {
+    submit: function () {
+      this.$refs.form.submit();
     },
-    props: ["action", "placeholder"],
-    name: "sidenav-search",
-  }
-  // FIXME: implement suggestions etc, use "loading" attribute
+  },
+  props: {
+    action: {
+      type: String,
+      required: true,
+    },
+    placeholder: {
+      type: String,
+      required: true,
+    },
+  },
+  name: "SidenavSearch",
+};
+// FIXME: implement suggestions etc, use "loading" attribute
 </script>
 
 <template>
   <form method="get" ref="form" :action="action" id="search-form">
     <v-text-field
-        :append-icon="'mdi-magnify'" @click:append="submit" single-line
-        id="search" name="q" type="search" enterkeyhint="search" :placeholder="placeholder"
+      :append-icon="'mdi-magnify'"
+      @click:append="submit"
+      single-line
+      id="search"
+      name="q"
+      type="search"
+      enterkeyhint="search"
+      :placeholder="placeholder"
     ></v-text-field>
   </form>
 </template>
diff --git a/aleksis/core/assets/components/notifications/NotificationItem.vue b/aleksis/core/assets/components/notifications/NotificationItem.vue
index 2035cbd69..3659f1f27 100644
--- a/aleksis/core/assets/components/notifications/NotificationItem.vue
+++ b/aleksis/core/assets/components/notifications/NotificationItem.vue
@@ -3,10 +3,8 @@
     :mutation="require('./markNotificationRead.graphql')"
     :variables="{ id: this.notification.id }"
   >
-    <template v-slot="{ mutate, loading, error }">
-      <v-list-item
-        v-intersect="mutate"
-      >
+    <template #default="{ mutate, loading, error }">
+      <v-list-item v-intersect="mutate">
         <v-list-item-content>
           <v-list-item-title>{{ notification.title }}</v-list-item-title>
 
@@ -22,7 +20,7 @@
 
         <v-list-item-action v-if="notification.link">
           <v-btn text :href="notification.link">
-            {{ $t('notifications.more_information') }} →
+            {{ $t("notifications.more_information") }} →
           </v-btn>
         </v-list-item-action>
 
@@ -35,9 +33,12 @@
 </template>
 
 <script>
-  export default {
-    props: {
-      notification: Object,
+export default {
+  props: {
+    notification: {
+      type: Object,
+      required: true,
     },
-  }
+  },
+};
 </script>
diff --git a/aleksis/core/assets/components/notifications/NotificationList.vue b/aleksis/core/assets/components/notifications/NotificationList.vue
index cb42dc17a..9e057ef7f 100644
--- a/aleksis/core/assets/components/notifications/NotificationList.vue
+++ b/aleksis/core/assets/components/notifications/NotificationList.vue
@@ -1,9 +1,9 @@
 <template>
   <ApolloQuery
     :query="require('./myNotifications.graphql')"
-    :pollInterval="1000"
+    :poll-interval="1000"
   >
-    <template v-slot="{ result: { error, data }, isLoading }">
+    <template #default="{ result: { error, data }, isLoading }">
       <v-list two-line v-if="data && data.myNotifications.notifications.length">
         <NotificationItem
           v-for="notification in data.myNotifications.notifications"
@@ -11,17 +11,19 @@
           :notification="notification"
         />
       </v-list>
-      <p v-else>{{ $root.django.gettext('No notifications available yet.') }}</p>
+      <p v-else>
+        {{ $root.django.gettext("No notifications available yet.") }}
+      </p>
     </template>
   </ApolloQuery>
 </template>
 
 <script>
-  import NotificationItem from "./NotificationItem.vue";
+import NotificationItem from "./NotificationItem.vue";
 
-  export default {
-    components: {
-      NotificationItem,
-    },
-  }
+export default {
+  components: {
+    NotificationItem,
+  },
+};
 </script>
diff --git a/aleksis/core/assets/css/global.scss b/aleksis/core/assets/css/global.scss
index 9c91c57dd..a0bcf72b5 100644
--- a/aleksis/core/assets/css/global.scss
+++ b/aleksis/core/assets/css/global.scss
@@ -2,7 +2,14 @@
 // HEADINGS //
 //////////////
 
-p, h1, h2, h3, h4, h5, h6, .card-title {
+p,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.card-title {
   overflow-wrap: break-word;
   hyphens: auto;
 }
diff --git a/aleksis/core/assets/index.js b/aleksis/core/assets/index.js
index 94f4131ba..47ec91d13 100644
--- a/aleksis/core/assets/index.js
+++ b/aleksis/core/assets/index.js
@@ -1,4 +1,4 @@
-import '@mdi/font/css/materialdesignicons.css'
+import "@mdi/font/css/materialdesignicons.css";
 
-import "./util"
-import "./app"
+import "./util";
+import "./app";
diff --git a/aleksis/core/assets/messages.json b/aleksis/core/assets/messages.json
index 34c7dab87..32108b2be 100644
--- a/aleksis/core/assets/messages.json
+++ b/aleksis/core/assets/messages.json
@@ -1,6 +1,6 @@
 {
   "en": {
-    "notifications":  {
+    "notifications": {
       "more_information": "More information",
       "no_notifications": "No notifications available yet."
     },
@@ -18,4 +18,3 @@
     }
   }
 }
-
diff --git a/aleksis/core/assets/util.js b/aleksis/core/assets/util.js
index 1b5041b21..a16f92bed 100644
--- a/aleksis/core/assets/util.js
+++ b/aleksis/core/assets/util.js
@@ -1,153 +1,16 @@
-/*
-commented out to see if something breaks
-// Define maps between Python's strftime and Luxon's and Materialize's proprietary formats
-const pythonToMomentJs = {
-    "%a": "EEE",
-    "%A": "EEEE",
-    "%w": "E",
-    "%d": "dd",
-    "%b": "MMM",
-    "%B": "MMMM",
-    "%m": "MM",
-    "%y": "yy",
-    "%Y": "yyyy",
-    "%H": "HH",
-    "%I": "hh",
-    "%p": "a",
-    "%M": "mm",
-    "%s": "ss",
-    "%f": "SSSSSS",
-    "%z": "ZZZ",
-    "%Z": "z",
-    "%U": "WW",
-    "%j": "ooo",
-    "%W": "WW",
-    "%u": "E",
-    "%G": "kkkk",
-    "%V": "WW",
-};
-
-const pythonToMaterialize = {
-    "%d": "dd",
-    "%a": "ddd",
-    "%A": "dddd",
-    "%m": "mm",
-    "%b": "mmm",
-    "%B": "mmmm",
-    "%y": "yy",
-    "%Y": "yyyy",
-}
-
-function buildDateFormat(formatString, map) {
-    // Convert a Python strftime format string to another format string
-    for (const key in map) {
-        formatString = formatString.replace(key, map[key]);
-    }
-    return formatString;
-}
-
-function initDatePicker(sel) {
-    // Initialize datepicker [MAT]
-
-    // Get the date format from Django
-    const dateInputFormat = get_format('DATE_INPUT_FORMATS')[0]
-    const inputFormat = buildDateFormat(dateInputFormat, pythonToMomentJs);
-    const outputFormat = buildDateFormat(dateInputFormat, pythonToMaterialize);
-
-    const el = $(sel).datepicker({
-        format: outputFormat,
-        // Pull translations from Django helpers
-        i18n: {
-            months: calendarweek_i18n.month_names,
-            monthsShort: calendarweek_i18n.month_abbrs,
-            weekdays: calendarweek_i18n.day_names,
-            weekdaysShort: calendarweek_i18n.day_abbrs,
-            weekdaysAbbrev: calendarweek_i18n.day_abbrs.map(([v]) => v),
-
-            // Buttons
-            today: gettext('Today'),
-            cancel: gettext('Cancel'),
-            done: gettext('OK'),
-        },
-
-        // Set monday as first day of week
-        firstDay: get_format('FIRST_DAY_OF_WEEK'),
-        autoClose: true,
-        yearRange: [new Date().getFullYear() - 100, new Date().getFullYear() + 100],
-    });
-
-    // Set initial values of datepickers
-    $(sel).each(function () {
-        const currentValue = $(this).val();
-        if (currentValue) {
-            const currentDate = luxon.DateTime.fromFormat(currentValue, inputFormat).toJSDate();
-            $(this).datepicker('setDate', currentDate);
-        }
-    });
-
-    return el;
-}
-
-function initTimePicker(sel) {
-    // Initialize timepicker [MAT]
-    return $(sel).timepicker({
-        twelveHour: false,
-        autoClose: true,
-        i18n: {
-            cancel: 'Abbrechen',
-            clear: 'Löschen',
-            done: 'OK'
-        },
-    });
-}
-*/
-
-
-$(document).ready(function () {
-
-    // If JS is activated, the language form will be auto-submitted
-    $('.language-field select').change(function () {
-        $(this).parents(".language-form").submit();
-    });
-
-    // If auto-submit is activated (see above), the language submit must not be visible
-    $(".language-submit-p").hide();
-
-    // Initalize print button
-    $("#print").click(function () {
-        window.print();
-    });
-
-    // Sync color picker
-    $(".jscolor").change(function () {
-        $("#" + $(this).data("preview")).css("color", $(this).val());
-    });
-
-    // Initialise auto-completion for search bar
-    window.autocomplete = new Autocomplete({minimum_length: 2});
-    window.autocomplete.setup();
-
-    // Initialize text collapsibles [MAT, own work]
-    $(".text-collapsible").addClass("closed").removeClass("opened");
-
-    $(".text-collapsible .open-icon").click(function (e) {
-        var el = $(e.target).parent();
-        el.addClass("opened").removeClass("closed");
-    });
-    $(".text-collapsible .close-icon").click(function (e) {
-        var el = $(e.target).parent();
-        el.addClass("closed").removeClass("opened");
-    });
-
-    // Initialize the service worker
-    if ('serviceWorker' in navigator) {
-        console.debug("Start registration of service worker.");
-        navigator.serviceWorker.register('/serviceworker.js', {
-            scope: '/'
-        }).then(function () {
-            console.debug("Service worker has been registered.");
-        }).catch(function () {
-            console.debug("Service worker registration has failed.")
-        });
-    }
+window.addEventListener("DOMContentLoaded", function () {
+  // Initialize the service worker
+  if ("serviceWorker" in navigator) {
+    console.debug("Start registration of service worker.");
+    navigator.serviceWorker
+      .register("/serviceworker.js", {
+        scope: "/",
+      })
+      .then(function () {
+        console.debug("Service worker has been registered.");
+      })
+      .catch(function () {
+        console.debug("Service worker registration has failed.");
+      });
+  }
 });
diff --git a/aleksis/core/static/js/copy_button.js b/aleksis/core/static/js/copy_button.js
index 554f6230e..c7f53e61b 100644
--- a/aleksis/core/static/js/copy_button.js
+++ b/aleksis/core/static/js/copy_button.js
@@ -1,16 +1,16 @@
 $(".copy-button").click((e) => {
-    const target = $(e.currentTarget);
-    const input = $("#" + target.data("target"));
-    const copy_icon = target.children(".copy-icon-copy").first();
-    const check_icon = target.children(".copy-icon-success").first();
+  const target = $(e.currentTarget);
+  const input = $("#" + target.data("target"));
+  const copy_icon = target.children(".copy-icon-copy").first();
+  const check_icon = target.children(".copy-icon-success").first();
 
-    console.log("Copying to clipboard");
-    navigator.clipboard.writeText(input.val()).then(r => {
-        check_icon.show();
-        copy_icon.hide();
-        setTimeout(() => {
-            check_icon.hide();
-            copy_icon.show();
-        }, 1000);
-    });
+  console.log("Copying to clipboard");
+  navigator.clipboard.writeText(input.val()).then((r) => {
+    check_icon.show();
+    copy_icon.hide();
+    setTimeout(() => {
+      check_icon.hide();
+      copy_icon.show();
+    }, 1000);
+  });
 });
diff --git a/aleksis/core/static/js/edit_dashboard.js b/aleksis/core/static/js/edit_dashboard.js
index 0cc90de60..b6e441191 100644
--- a/aleksis/core/static/js/edit_dashboard.js
+++ b/aleksis/core/static/js/edit_dashboard.js
@@ -1,22 +1,22 @@
 function refreshOrder() {
-    $(".order-input").val(0);
-    $("#widgets > .col").each(function (index) {
-        const order = (index + 1) * 10;
-        let pk = $(this).attr("data-pk");
-        let sel = $("#order-form input[value=" + pk + "].pk-input").next();
-        sel.val(order);
-    })
+  $(".order-input").val(0);
+  $("#widgets > .col").each(function (index) {
+    const order = (index + 1) * 10;
+    let pk = $(this).attr("data-pk");
+    let sel = $("#order-form input[value=" + pk + "].pk-input").next();
+    sel.val(order);
+  });
 }
 
 $(document).ready(function () {
-    $('#not-used-widgets').sortable({
-        group: 'widgets',
-        animation: 150,
-        onEnd: refreshOrder
-    });
-    $('#widgets').sortable({
-        group: 'widgets',
-        animation: 150,
-        onEnd: refreshOrder
-    });
+  $("#not-used-widgets").sortable({
+    group: "widgets",
+    animation: 150,
+    onEnd: refreshOrder,
+  });
+  $("#widgets").sortable({
+    group: "widgets",
+    animation: 150,
+    onEnd: refreshOrder,
+  });
 });
diff --git a/aleksis/core/static/js/helper.js b/aleksis/core/static/js/helper.js
index 844496346..48dab9565 100644
--- a/aleksis/core/static/js/helper.js
+++ b/aleksis/core/static/js/helper.js
@@ -1,30 +1,37 @@
 function formatDate(date) {
-    return date.getDate() + "." + (date.getMonth() + 1) + "." + date.getFullYear();
+  return (
+    date.getDate() + "." + (date.getMonth() + 1) + "." + date.getFullYear()
+  );
 }
 
-
 function addZeros(i) {
-    if (i < 10) {
-        return "0" + i;
-    } else {
-        return "" + i;
-    }
+  if (i < 10) {
+    return "0" + i;
+  } else {
+    return "" + i;
+  }
 }
 
 function formatDateForDjango(date) {
-    return "" + date.getFullYear() + "/" + addZeros(date.getMonth() + 1) + "/" + addZeros(date.getDate()) + "/";
-
+  return (
+    "" +
+    date.getFullYear() +
+    "/" +
+    addZeros(date.getMonth() + 1) +
+    "/" +
+    addZeros(date.getDate()) +
+    "/"
+  );
 }
 
 function getNow() {
-    return new Date();
+  return new Date();
 }
 
 function getNowFormatted() {
-    return formatDate(getNow());
+  return formatDate(getNow());
 }
 
 function getJSONScript(elementId) {
-    return JSON.parse(document.getElementById(elementId).textContent);
+  return JSON.parse(document.getElementById(elementId).textContent);
 }
-
diff --git a/aleksis/core/static/js/include_ajax_live.js b/aleksis/core/static/js/include_ajax_live.js
index 3a4794bad..0d23769c1 100644
--- a/aleksis/core/static/js/include_ajax_live.js
+++ b/aleksis/core/static/js/include_ajax_live.js
@@ -14,7 +14,7 @@ const setAsyncInterval = (cb, interval) => {
     runAsyncInterval(cb, interval, intervalIndex);
     return intervalIndex;
   } else {
-    throw new Error('Callback must be a function');
+    throw new Error("Callback must be a function");
   }
 };
 
@@ -25,11 +25,11 @@ const clearAsyncInterval = (intervalIndex) => {
 };
 
 let live_load_interval = setAsyncInterval(async () => {
-  console.log('fetching new data');
+  console.log("fetching new data");
   const promise = new Promise((resolve) => {
-    $('#live_load').load(window.location.pathname + " #live_load");
+    $("#live_load").load(window.location.pathname + " #live_load");
     resolve(1);
   });
   await promise;
-  console.log('data fetched successfully');
+  console.log("data fetched successfully");
 }, 15000);
diff --git a/aleksis/core/static/js/main.js b/aleksis/core/static/js/main.js
index b735e2bb5..c8ebd1383 100644
--- a/aleksis/core/static/js/main.js
+++ b/aleksis/core/static/js/main.js
@@ -1,197 +1,209 @@
 // Define maps between Python's strftime and Luxon's and Materialize's proprietary formats
 const pythonToMomentJs = {
-    "%a": "EEE",
-    "%A": "EEEE",
-    "%w": "E",
-    "%d": "dd",
-    "%b": "MMM",
-    "%B": "MMMM",
-    "%m": "MM",
-    "%y": "yy",
-    "%Y": "yyyy",
-    "%H": "HH",
-    "%I": "hh",
-    "%p": "a",
-    "%M": "mm",
-    "%s": "ss",
-    "%f": "SSSSSS",
-    "%z": "ZZZ",
-    "%Z": "z",
-    "%U": "WW",
-    "%j": "ooo",
-    "%W": "WW",
-    "%u": "E",
-    "%G": "kkkk",
-    "%V": "WW",
+  "%a": "EEE",
+  "%A": "EEEE",
+  "%w": "E",
+  "%d": "dd",
+  "%b": "MMM",
+  "%B": "MMMM",
+  "%m": "MM",
+  "%y": "yy",
+  "%Y": "yyyy",
+  "%H": "HH",
+  "%I": "hh",
+  "%p": "a",
+  "%M": "mm",
+  "%s": "ss",
+  "%f": "SSSSSS",
+  "%z": "ZZZ",
+  "%Z": "z",
+  "%U": "WW",
+  "%j": "ooo",
+  "%W": "WW",
+  "%u": "E",
+  "%G": "kkkk",
+  "%V": "WW",
 };
 
 const pythonToMaterialize = {
-    "%d": "dd",
-    "%a": "ddd",
-    "%A": "dddd",
-    "%m": "mm",
-    "%b": "mmm",
-    "%B": "mmmm",
-    "%y": "yy",
-    "%Y": "yyyy",
-}
+  "%d": "dd",
+  "%a": "ddd",
+  "%A": "dddd",
+  "%m": "mm",
+  "%b": "mmm",
+  "%B": "mmmm",
+  "%y": "yy",
+  "%Y": "yyyy",
+};
 
 function buildDateFormat(formatString, map) {
-    // Convert a Python strftime format string to another format string
-    for (const key in map) {
-        formatString = formatString.replace(key, map[key]);
-    }
-    return formatString;
+  // Convert a Python strftime format string to another format string
+  for (const key in map) {
+    formatString = formatString.replace(key, map[key]);
+  }
+  return formatString;
 }
 
 function initDatePicker(sel) {
-    // Initialize datepicker [MAT]
-
-    // Get the date format from Django
-    const dateInputFormat = get_format('DATE_INPUT_FORMATS')[0]
-    const inputFormat = buildDateFormat(dateInputFormat, pythonToMomentJs);
-    const outputFormat = buildDateFormat(dateInputFormat, pythonToMaterialize);
-
-    const el = $(sel).datepicker({
-        format: outputFormat,
-        // Pull translations from Django helpers
-        i18n: {
-            months: calendarweek_i18n.month_names,
-            monthsShort: calendarweek_i18n.month_abbrs,
-            weekdays: calendarweek_i18n.day_names,
-            weekdaysShort: calendarweek_i18n.day_abbrs,
-            weekdaysAbbrev: calendarweek_i18n.day_abbrs.map(([v]) => v),
-
-            // Buttons
-            today: gettext('Today'),
-            cancel: gettext('Cancel'),
-            done: gettext('OK'),
-        },
-
-        // Set monday as first day of week
-        firstDay: get_format('FIRST_DAY_OF_WEEK'),
-        autoClose: true,
-        yearRange: [new Date().getFullYear() - 100, new Date().getFullYear() + 100],
-    });
-
-    // Set initial values of datepickers
-    $(sel).each(function () {
-        const currentValue = $(this).val();
-        if (currentValue) {
-            const currentDate = luxon.DateTime.fromFormat(currentValue, inputFormat).toJSDate();
-            $(this).datepicker('setDate', currentDate);
-        }
-    });
-
-    return el;
+  // Initialize datepicker [MAT]
+
+  // Get the date format from Django
+  const dateInputFormat = get_format("DATE_INPUT_FORMATS")[0];
+  const inputFormat = buildDateFormat(dateInputFormat, pythonToMomentJs);
+  const outputFormat = buildDateFormat(dateInputFormat, pythonToMaterialize);
+
+  const el = $(sel).datepicker({
+    format: outputFormat,
+    // Pull translations from Django helpers
+    i18n: {
+      months: calendarweek_i18n.month_names,
+      monthsShort: calendarweek_i18n.month_abbrs,
+      weekdays: calendarweek_i18n.day_names,
+      weekdaysShort: calendarweek_i18n.day_abbrs,
+      weekdaysAbbrev: calendarweek_i18n.day_abbrs.map(([v]) => v),
+
+      // Buttons
+      today: gettext("Today"),
+      cancel: gettext("Cancel"),
+      done: gettext("OK"),
+    },
+
+    // Set monday as first day of week
+    firstDay: get_format("FIRST_DAY_OF_WEEK"),
+    autoClose: true,
+    yearRange: [new Date().getFullYear() - 100, new Date().getFullYear() + 100],
+  });
+
+  // Set initial values of datepickers
+  $(sel).each(function () {
+    const currentValue = $(this).val();
+    if (currentValue) {
+      const currentDate = luxon.DateTime.fromFormat(
+        currentValue,
+        inputFormat
+      ).toJSDate();
+      $(this).datepicker("setDate", currentDate);
+    }
+  });
+
+  return el;
 }
 
 function initTimePicker(sel) {
-    // Initialize timepicker [MAT]
-    return $(sel).timepicker({
-        twelveHour: false,
-        autoClose: true,
-        i18n: {
-            cancel: 'Abbrechen',
-            clear: 'Löschen',
-            done: 'OK'
-        },
-    });
+  // Initialize timepicker [MAT]
+  return $(sel).timepicker({
+    twelveHour: false,
+    autoClose: true,
+    i18n: {
+      cancel: "Abbrechen",
+      clear: "Löschen",
+      done: "OK",
+    },
+  });
 }
 
 $(document).ready(function () {
-    $("dmc-datetime input").addClass("datepicker");
-    $("[data-form-control='date']").addClass("datepicker");
-    $("[data-form-control='time']").addClass("timepicker");
-
-    // Initialize sidenav [MAT]
-    $(".sidenav").sidenav();
-
-    // Initialize datepicker [MAT]
-    initDatePicker(".datepicker");
-
-    // Initialize timepicker [MAT]
-    initTimePicker(".timepicker");
-
-    // Initialize tooltip [MAT]
-    $('.tooltipped').tooltip();
-
-    // Initialize select [MAT]
-    $('select').formSelect();
-
-    // Initialize dropdown [MAT]
-    $('.dropdown-trigger').dropdown();
-    $('.navbar-dropdown-trigger').dropdown({
-        "coverTrigger": false,
-        "constrainWidth": false,
-    });
-
-    // If JS is activated, the language form will be auto-submitted
-    $('.language-field select').change(function () {
-        $(this).parents(".language-form").submit();
-    });
-
-    // If auto-submit is activated (see above), the language submit must not be visible
-    $(".language-submit-p").hide();
-
-    // Initalize print button
-    $("#print").click(function () {
-        window.print();
-    });
-
-    // Initialize Collapsible [MAT]
-    $('.collapsible').collapsible();
-
-    // Initialize FABs [MAT]
-    $('.fixed-action-btn').floatingActionButton();
-
-    // Initialize Modals [MAT]
-    $('.modal').modal();
-
-    // Initialize image boxes [Materialize]
-    $('.materialboxed').materialbox();
-
-    // Intialize Tabs [Materialize]
-    $('.tabs').tabs();
-
-    // Sync color picker
-    $(".jscolor").change(function () {
-        $("#" + $(this).data("preview")).css("color", $(this).val());
-    });
-
-    // Initialise auto-completion for search bar
-    window.autocomplete = new Autocomplete({minimum_length: 2});
-    window.autocomplete.setup();
-
-    // Initialize text collapsibles [MAT, own work]
-    $(".text-collapsible").addClass("closed").removeClass("opened");
-
-    $(".text-collapsible .open-icon").click(function (e) {
-        var el = $(e.target).parent();
-        el.addClass("opened").removeClass("closed");
-    });
-    $(".text-collapsible .close-icon").click(function (e) {
-        var el = $(e.target).parent();
-        el.addClass("closed").removeClass("opened");
-    });
-
-    // Initialize the service worker
-    if ('serviceWorker' in navigator) {
-        console.debug("Start registration of service worker.");
-        navigator.serviceWorker.register('/serviceworker.js', {
-            scope: '/'
-        }).then(function() {
-            console.debug("Service worker has been registered.");
-        }).catch(function() {
-            console.debug("Service worker registration has failed.")
-        });
-    }
+  $("dmc-datetime input").addClass("datepicker");
+  $("[data-form-control='date']").addClass("datepicker");
+  $("[data-form-control='time']").addClass("timepicker");
+
+  // Initialize sidenav [MAT]
+  $(".sidenav").sidenav();
+
+  // Initialize datepicker [MAT]
+  initDatePicker(".datepicker");
+
+  // Initialize timepicker [MAT]
+  initTimePicker(".timepicker");
+
+  // Initialize tooltip [MAT]
+  $(".tooltipped").tooltip();
+
+  // Initialize select [MAT]
+  $("select").formSelect();
+
+  // Initialize dropdown [MAT]
+  $(".dropdown-trigger").dropdown();
+  $(".navbar-dropdown-trigger").dropdown({
+    coverTrigger: false,
+    constrainWidth: false,
+  });
+
+  // If JS is activated, the language form will be auto-submitted
+  $(".language-field select").change(function () {
+    $(this).parents(".language-form").submit();
+  });
+
+  // If auto-submit is activated (see above), the language submit must not be visible
+  $(".language-submit-p").hide();
+
+  // Initalize print button
+  $("#print").click(function () {
+    window.print();
+  });
+
+  // Initialize Collapsible [MAT]
+  $(".collapsible").collapsible();
+
+  // Initialize FABs [MAT]
+  $(".fixed-action-btn").floatingActionButton();
+
+  // Initialize Modals [MAT]
+  $(".modal").modal();
+
+  // Initialize image boxes [Materialize]
+  $(".materialboxed").materialbox();
+
+  // Intialize Tabs [Materialize]
+  $(".tabs").tabs();
+
+  // Sync color picker
+  $(".jscolor").change(function () {
+    $("#" + $(this).data("preview")).css("color", $(this).val());
+  });
+
+  // Initialise auto-completion for search bar
+  window.autocomplete = new Autocomplete({ minimum_length: 2 });
+  window.autocomplete.setup();
+
+  // Initialize text collapsibles [MAT, own work]
+  $(".text-collapsible").addClass("closed").removeClass("opened");
+
+  $(".text-collapsible .open-icon").click(function (e) {
+    var el = $(e.target).parent();
+    el.addClass("opened").removeClass("closed");
+  });
+  $(".text-collapsible .close-icon").click(function (e) {
+    var el = $(e.target).parent();
+    el.addClass("closed").removeClass("opened");
+  });
+
+  // Initialize the service worker
+  if ("serviceWorker" in navigator) {
+    console.debug("Start registration of service worker.");
+    navigator.serviceWorker
+      .register("/serviceworker.js", {
+        scope: "/",
+      })
+      .then(function () {
+        console.debug("Service worker has been registered.");
+      })
+      .catch(function () {
+        console.debug("Service worker registration has failed.");
+      });
+  }
 });
 
 // Show notice if serviceworker broadcasts that the current page comes from its cache
 const channel = new BroadcastChannel("cache-or-not");
-channel.addEventListener("message", event => {
-    if ((event.data) && !($("#cache-alert").length)) {
-        $("main").prepend('<div id="cache-alert" class="alert warning"><p><i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>' + gettext("This page may contain outdated information since there is no internet connection.") + '</p> </div>')
-    }
+channel.addEventListener("message", (event) => {
+  if (event.data && !$("#cache-alert").length) {
+    $("main").prepend(
+      '<div id="cache-alert" class="alert warning"><p><i class="material-icons iconify left" data-icon="mdi:alert-outline"></i>' +
+        gettext(
+          "This page may contain outdated information since there is no internet connection."
+        ) +
+        "</p> </div>"
+    );
+  }
 });
diff --git a/aleksis/core/static/js/multi_select.js b/aleksis/core/static/js/multi_select.js
index cddf911b5..105f4c13d 100644
--- a/aleksis/core/static/js/multi_select.js
+++ b/aleksis/core/static/js/multi_select.js
@@ -1,48 +1,48 @@
 $(document).ready(function () {
-    $(".select--header-box").change(function () {
-        /*
+  $(".select--header-box").change(function () {
+    /*
         If the top checkbox is checked, all sub checkboxes should be checked,
         if it gets unchecked, all other ones should get unchecked.
         */
-        if ($(this).is(":checked")) {
-            $(this).closest("table").find('input[name="selected_objects"]').prop({
-                indeterminate: false,
-                checked: true,
-            });
-        } else {
-            $(this).closest("table").find('input[name="selected_objects"]').prop({
-                indeterminate: false,
-                checked: false,
-            });
-        }
-    });
+    if ($(this).is(":checked")) {
+      $(this).closest("table").find('input[name="selected_objects"]').prop({
+        indeterminate: false,
+        checked: true,
+      });
+    } else {
+      $(this).closest("table").find('input[name="selected_objects"]').prop({
+        indeterminate: false,
+        checked: false,
+      });
+    }
+  });
 
-    $('input[name="selected_objects"]').change(function () {
-        /*
+  $('input[name="selected_objects"]').change(function () {
+    /*
         If a table checkbox changes, check the state of the other ones.
         If all boxes are checked the box in the header should be checked,
         if all boxes are unchecked the header box should be unchecked. If
         only some boxes are checked the top one should be inderteminate.
          */
-        let checked = $(this).is(":checked");
-        let indeterminate = false;
-        let table = $(this).closest("table");
-        table.find('input[name="selected_objects"]').each(function () {
-            if ($(this).is(":checked") !== checked) {
-                /* Set the header box to indeterminate if the boxes are not the same */
-                table.find(".select--header-box").prop({
-                    indeterminate: true,
-                })
-                indeterminate = true;
-                return false;
-            }
+    let checked = $(this).is(":checked");
+    let indeterminate = false;
+    let table = $(this).closest("table");
+    table.find('input[name="selected_objects"]').each(function () {
+      if ($(this).is(":checked") !== checked) {
+        /* Set the header box to indeterminate if the boxes are not the same */
+        table.find(".select--header-box").prop({
+          indeterminate: true,
         });
-        if (!(indeterminate)) {
-            /* All boxes are the same, set the header box to the same value */
-            table.find(".select--header-box").prop({
-                indeterminate: false,
-                checked: checked,
-            });
-        }
+        indeterminate = true;
+        return false;
+      }
     });
+    if (!indeterminate) {
+      /* All boxes are the same, set the header box to the same value */
+      table.find(".select--header-box").prop({
+        indeterminate: false,
+        checked: checked,
+      });
+    }
+  });
 });
diff --git a/aleksis/core/static/js/progress.js b/aleksis/core/static/js/progress.js
index 8a97577e9..dc554ec8f 100644
--- a/aleksis/core/static/js/progress.js
+++ b/aleksis/core/static/js/progress.js
@@ -1,84 +1,100 @@
 const OPTIONS = getJSONScript("progress_options");
 
 const STYLE_CLASSES = {
-    10: 'info',
-    20: 'info',
-    25: 'success',
-    30: 'warning',
-    40: 'error',
+  10: "info",
+  20: "info",
+  25: "success",
+  30: "warning",
+  40: "error",
 };
 
 const ICONS = {
-    10: 'mdi:information',
-    20: 'mdi:information',
-    25: 'mdi:check-circle',
-    30: 'mdi:alert-outline',
-    40: 'mdi:alert-octagon-outline',
+  10: "mdi:information",
+  20: "mdi:information",
+  25: "mdi:check-circle",
+  30: "mdi:alert-outline",
+  40: "mdi:alert-octagon-outline",
 };
 
 function setProgress(progress) {
-    $("#progress-bar").css("width", progress + "%");
+  $("#progress-bar").css("width", progress + "%");
 }
 
 function renderMessageBox(level, text) {
-    return '<div class="alert ' + STYLE_CLASSES[level] + '"><p><i class="material-icons iconify left" data-icon="' + ICONS[level] + '"></i>' + text + '</p></div>';
+  return (
+    '<div class="alert ' +
+    STYLE_CLASSES[level] +
+    '"><p><i class="material-icons iconify left" data-icon="' +
+    ICONS[level] +
+    '"></i>' +
+    text +
+    "</p></div>"
+  );
 }
 
 function updateMessages(messages) {
-    const messagesBox = $("#messages");
+  const messagesBox = $("#messages");
 
-    // Clear container
-    messagesBox.html("");
+  // Clear container
+  messagesBox.html("");
 
-    // Render message boxes
-    $.each(messages, function (i, message) {
-        messagesBox.append(renderMessageBox(message[0], message[1]));
-    });
+  // Render message boxes
+  $.each(messages, function (i, message) {
+    messagesBox.append(renderMessageBox(message[0], message[1]));
+  });
 }
 
-function customProgress(progressBarElement, progressBarMessageElement, progress) {
-    setProgress(progress.percent);
+function customProgress(
+  progressBarElement,
+  progressBarMessageElement,
+  progress
+) {
+  setProgress(progress.percent);
 
-    if (progress.hasOwnProperty("messages")) {
-        updateMessages(progress.messages);
-    }
+  if (progress.hasOwnProperty("messages")) {
+    updateMessages(progress.messages);
+  }
 }
 
-
 function customSuccess(progressBarElement, progressBarMessageElement, result) {
-    setProgress(100);
-    if (result) {
-        updateMessages(result);
-    }
-    $("#result-alert").addClass("success");
-    $("#result-icon").attr("data-icon", "mdi:check-circle-outline");
-    $("#result-text").text(OPTIONS.success);
-    $("#result-box").show();
-    $("#result-button").show();
-    const redirect = "redirect_on_success" in OPTIONS && OPTIONS.redirect_on_success;
-    if (redirect) {
-        window.location.replace(OPTIONS.redirect_on_success);
-    }
+  setProgress(100);
+  if (result) {
+    updateMessages(result);
+  }
+  $("#result-alert").addClass("success");
+  $("#result-icon").attr("data-icon", "mdi:check-circle-outline");
+  $("#result-text").text(OPTIONS.success);
+  $("#result-box").show();
+  $("#result-button").show();
+  const redirect =
+    "redirect_on_success" in OPTIONS && OPTIONS.redirect_on_success;
+  if (redirect) {
+    window.location.replace(OPTIONS.redirect_on_success);
+  }
 }
 
-function customError(progressBarElement, progressBarMessageElement, excMessage) {
-    setProgress(100);
-    if (excMessage) {
-        updateMessages([40, excMessage]);
-    }
-    $("#result-alert").addClass("error");
-    $("#result-icon").attr("data-icon", "mdi:alert-octagon-outline");
-    $("#result-text").text(OPTIONS.error);
-    $("#result-box").show();
+function customError(
+  progressBarElement,
+  progressBarMessageElement,
+  excMessage
+) {
+  setProgress(100);
+  if (excMessage) {
+    updateMessages([40, excMessage]);
+  }
+  $("#result-alert").addClass("error");
+  $("#result-icon").attr("data-icon", "mdi:alert-octagon-outline");
+  $("#result-text").text(OPTIONS.error);
+  $("#result-box").show();
 }
 
 $(document).ready(function () {
-    $("#progress-bar").removeClass("indeterminate").addClass("determinate");
+  $("#progress-bar").removeClass("indeterminate").addClass("determinate");
 
-    var progressUrl = Urls["taskStatus"](OPTIONS.task_id);
-    CeleryProgressBar.initProgressBar(progressUrl, {
-        onProgress: customProgress,
-        onSuccess: customSuccess,
-        onError: customError,
-    });
+  var progressUrl = Urls["taskStatus"](OPTIONS.task_id);
+  CeleryProgressBar.initProgressBar(progressUrl, {
+    onProgress: customProgress,
+    onSuccess: customSuccess,
+    onError: customError,
+  });
 });
diff --git a/aleksis/core/static/js/search.js b/aleksis/core/static/js/search.js
index fd121dbdf..1841829ec 100644
--- a/aleksis/core/static/js/search.js
+++ b/aleksis/core/static/js/search.js
@@ -6,129 +6,129 @@
  */
 
 var Autocomplete = function (options) {
-    this.form_selector = options.form_selector || '.autocomplete';
-    this.url = options.url || Urls.searchbarSnippets();
-    this.delay = parseInt(options.delay || 300);
-    this.minimum_length = parseInt(options.minimum_length || 3);
-    this.form_elem = null;
-    this.query_box = null;
-    this.selected_element = null;
+  this.form_selector = options.form_selector || ".autocomplete";
+  this.url = options.url || Urls.searchbarSnippets();
+  this.delay = parseInt(options.delay || 300);
+  this.minimum_length = parseInt(options.minimum_length || 3);
+  this.form_elem = null;
+  this.query_box = null;
+  this.selected_element = null;
 };
 
 Autocomplete.prototype.setup = function () {
-    var self = this;
-
-    this.form_elem = $(this.form_selector);
-    this.query_box = this.form_elem.find('input[name=q]');
-
-
-    $("#search-form").focusout(function (e) {
-        if (!$(e.relatedTarget).hasClass("search-item")) {
-            e.preventDefault();
-            $("#search-results").remove();
-        }
-    });
-
-    // Trigger the "keyup" event if input gets focused
-
-    this.query_box.focus(function () {
-        self.query_box.trigger("input");
-    });
-
-    this.query_box.on("input", () => {
-        console.log("Input changed, fetching again...")
-        var query = self.query_box.val();
-
-        if (query.length < self.minimum_length) {
-            $("#search-results").remove();
-            return true;
-        }
-
-        self.fetch(query);
-        return true;
-    });
-
-    // Watch the input box.
-    this.query_box.keydown(function (e) {
-
-        if (e.which === 38) { // Keypress Up
-            if (!self.selected_element) {
-                self.setSelectedResult($("#search-collection").children().last());
-                return false;
-            }
-
-            let prev = self.selected_element.prev();
-            if (prev.length > 0) {
-                self.setSelectedResult(prev);
-            }
-            return false;
-        }
-
-        if (e.which === 40) { // Keypress Down
-            if (!self.selected_element) {
-                self.setSelectedResult($("#search-collection").children().first());
-                return false;
-            }
-
-            let next = self.selected_element.next();
-            if (next.length > 0) {
-                self.setSelectedResult(next);
-            }
-            return false;
-        }
-
-        if (self.selected_element && e.which === 13) {
-            e.preventDefault();
-            window.location.href = self.selected_element.attr("href");
-        }
-    });
-
-    // // On selecting a result, remove result box
-    // this.form_elem.on('click', '#search-results', function (ev) {
-    //     $('#search-results').remove();
-    //     return true;
-    // });
-
-    // Disable browser's own autocomplete
-    // We do this here so users without JavaScript can keep it enabled
-    this.query_box.attr('autocomplete', 'off');
+  var self = this;
+
+  this.form_elem = $(this.form_selector);
+  this.query_box = this.form_elem.find("input[name=q]");
+
+  $("#search-form").focusout(function (e) {
+    if (!$(e.relatedTarget).hasClass("search-item")) {
+      e.preventDefault();
+      $("#search-results").remove();
+    }
+  });
+
+  // Trigger the "keyup" event if input gets focused
+
+  this.query_box.focus(function () {
+    self.query_box.trigger("input");
+  });
+
+  this.query_box.on("input", () => {
+    console.log("Input changed, fetching again...");
+    var query = self.query_box.val();
+
+    if (query.length < self.minimum_length) {
+      $("#search-results").remove();
+      return true;
+    }
+
+    self.fetch(query);
+    return true;
+  });
+
+  // Watch the input box.
+  this.query_box.keydown(function (e) {
+    if (e.which === 38) {
+      // Keypress Up
+      if (!self.selected_element) {
+        self.setSelectedResult($("#search-collection").children().last());
+        return false;
+      }
+
+      let prev = self.selected_element.prev();
+      if (prev.length > 0) {
+        self.setSelectedResult(prev);
+      }
+      return false;
+    }
+
+    if (e.which === 40) {
+      // Keypress Down
+      if (!self.selected_element) {
+        self.setSelectedResult($("#search-collection").children().first());
+        return false;
+      }
+
+      let next = self.selected_element.next();
+      if (next.length > 0) {
+        self.setSelectedResult(next);
+      }
+      return false;
+    }
+
+    if (self.selected_element && e.which === 13) {
+      e.preventDefault();
+      window.location.href = self.selected_element.attr("href");
+    }
+  });
+
+  // // On selecting a result, remove result box
+  // this.form_elem.on('click', '#search-results', function (ev) {
+  //     $('#search-results').remove();
+  //     return true;
+  // });
+
+  // Disable browser's own autocomplete
+  // We do this here so users without JavaScript can keep it enabled
+  this.query_box.attr("autocomplete", "off");
 };
 
 Autocomplete.prototype.fetch = function (query) {
-    var self = this;
-
-    $.ajax({
-        url: this.url,
-        data: {
-            'q': query
-        },
-        beforeSend: (request, settings) => {
-            $('#search-results').remove();
-            self.setLoader(true);
-        },
-        success: function (data) {
-            self.setLoader(false);
-            self.show_results(data);
-        }
-    })
+  var self = this;
+
+  $.ajax({
+    url: this.url,
+    data: {
+      q: query,
+    },
+    beforeSend: (request, settings) => {
+      $("#search-results").remove();
+      self.setLoader(true);
+    },
+    success: function (data) {
+      self.setLoader(false);
+      self.show_results(data);
+    },
+  });
 };
 
 Autocomplete.prototype.show_results = function (data) {
-    $('#search-results').remove();
-    var results_wrapper = $('<div id="search-results">' + data + '</div>');
-    this.query_box.after(results_wrapper);
-    this.selected_element = null;
+  $("#search-results").remove();
+  var results_wrapper = $('<div id="search-results">' + data + "</div>");
+  this.query_box.after(results_wrapper);
+  this.selected_element = null;
 };
 
 Autocomplete.prototype.setSelectedResult = function (element) {
-    if (this.selected_element) {
-        this.selected_element.removeClass("active");
-    }
-    element.addClass("active");
-    this.selected_element = element;
-    console.log("New element: ", element);
+  if (this.selected_element) {
+    this.selected_element.removeClass("active");
+  }
+  element.addClass("active");
+  this.selected_element = element;
+  console.log("New element: ", element);
 };
 
 Autocomplete.prototype.setLoader = function (value) {
-        $("#search-loader").css("display", (value === true ? "block" : "none"))
-}
+  $("#search-loader").css("display", value === true ? "block" : "none");
+};
diff --git a/aleksis/core/static/js/serviceworker.js b/aleksis/core/static/js/serviceworker.js
index 16382ec00..8fa870824 100644
--- a/aleksis/core/static/js/serviceworker.js
+++ b/aleksis/core/static/js/serviceworker.js
@@ -1,83 +1,89 @@
-
 // This is the AlekSIS service worker
 
-const CACHE = 'aleksis-cache';
+const CACHE = "aleksis-cache";
 
-const offlineFallbackPage = 'offline/';
+const offlineFallbackPage = "offline/";
 
-const channel = new BroadcastChannel('cache-or-not');
+const channel = new BroadcastChannel("cache-or-not");
 
 var comesFromCache = false;
 
 self.addEventListener("install", function (event) {
-    console.log("[AlekSIS PWA] Install Event processing.");
+  console.log("[AlekSIS PWA] Install Event processing.");
 
-    console.log("[AlekSIS PWA] Skipping waiting on install.");
-    self.skipWaiting();
+  console.log("[AlekSIS PWA] Skipping waiting on install.");
+  self.skipWaiting();
 
-    event.waitUntil(
-        caches.open(CACHE).then(function (cache) {
-            console.log("[AlekSIS PWA] Caching pages during install.");
-            return cache.add(offlineFallbackPage);
-        })
-    );
+  event.waitUntil(
+    caches.open(CACHE).then(function (cache) {
+      console.log("[AlekSIS PWA] Caching pages during install.");
+      return cache.add(offlineFallbackPage);
+    })
+  );
 });
 
 // Allow sw to control of current page
 self.addEventListener("activate", function (event) {
-    console.log("[AlekSIS PWA] Claiming clients for current page.");
-    event.waitUntil(self.clients.claim());
+  console.log("[AlekSIS PWA] Claiming clients for current page.");
+  event.waitUntil(self.clients.claim());
 });
 
 // If any fetch fails, it will look for the request in the cache and serve it from there first
 self.addEventListener("fetch", function (event) {
-    if (event.request.method !== "GET") return;
-    networkFirstFetch(event);
-    if (comesFromCache) channel.postMessage(true);
+  if (event.request.method !== "GET") return;
+  networkFirstFetch(event);
+  if (comesFromCache) channel.postMessage(true);
 });
 
 function networkFirstFetch(event) {
-    event.respondWith(
-        fetch(event.request)
-            .then(function (response) {
-                // If request was successful, add or update it in the cache
-                console.log("[AlekSIS PWA] Network request successful.");
-                event.waitUntil(updateCache(event.request, response.clone()));
-                comesFromCache = false;
-                return response;
-            })
-            .catch(function (error) {
-                console.log("[AlekSIS PWA] Network request failed. Serving content from cache: " + error);
-                return fromCache(event);
-            })
-    );
+  event.respondWith(
+    fetch(event.request)
+      .then(function (response) {
+        // If request was successful, add or update it in the cache
+        console.log("[AlekSIS PWA] Network request successful.");
+        event.waitUntil(updateCache(event.request, response.clone()));
+        comesFromCache = false;
+        return response;
+      })
+      .catch(function (error) {
+        console.log(
+          "[AlekSIS PWA] Network request failed. Serving content from cache: " +
+            error
+        );
+        return fromCache(event);
+      })
+  );
 }
 
 function fromCache(event) {
-    // Check to see if you have it in the cache
-    // Return response
-    // If not in the cache, then return offline fallback page
-    return caches.open(CACHE).then(function (cache) {
-        return cache.match(event.request)
-            .then(function (matching) {
-                if (!matching || matching.status === 404) {
-                    console.log("[AlekSIS PWA] Cache request failed. Serving offline fallback page.");
-                    comesFromCache = false;
-                    // Use the precached offline page as fallback
-                    return caches.match(offlineFallbackPage);
-                }
-                comesFromCache = true;
-                return matching;
-            });
+  // Check to see if you have it in the cache
+  // Return response
+  // If not in the cache, then return offline fallback page
+  return caches.open(CACHE).then(function (cache) {
+    return cache.match(event.request).then(function (matching) {
+      if (!matching || matching.status === 404) {
+        console.log(
+          "[AlekSIS PWA] Cache request failed. Serving offline fallback page."
+        );
+        comesFromCache = false;
+        // Use the precached offline page as fallback
+        return caches.match(offlineFallbackPage);
+      }
+      comesFromCache = true;
+      return matching;
     });
+  });
 }
 
 function updateCache(request, response) {
-    if (response.headers.get('cache-control') && response.headers.get('cache-control').includes('no-cache')) {
-        return Promise.resolve();
-    } else {
-        return caches.open(CACHE).then(function (cache) {
-            return cache.put(request, response);
-        });
-    }
+  if (
+    response.headers.get("cache-control") &&
+    response.headers.get("cache-control").includes("no-cache")
+  ) {
+    return Promise.resolve();
+  } else {
+    return caches.open(CACHE).then(function (cache) {
+      return cache.put(request, response);
+    });
+  }
 }
diff --git a/aleksis/core/static/print-simple.css b/aleksis/core/static/print-simple.css
index f0e6536b4..dfde8908d 100644
--- a/aleksis/core/static/print-simple.css
+++ b/aleksis/core/static/print-simple.css
@@ -1,21 +1,25 @@
 @page {
-    padding: 0;
-    margin: 0;
+  padding: 0;
+  margin: 0;
 }
 
-table.small-print, td.small-print, th.small-print {
-    font-size: 10pt;
+table.small-print,
+td.small-print,
+th.small-print {
+  font-size: 10pt;
 }
 
 tr {
-    border-bottom: 1px solid rgba(0, 0, 0, 0.3);
+  border-bottom: 1px solid rgba(0, 0, 0, 0.3);
 }
 
-td, th {
-    padding: 1px;
+td,
+th {
+  padding: 1px;
 }
 
-td.rotate, th.rotate {
-    text-align: center;
-    transform: rotate(-90deg);
+td.rotate,
+th.rotate {
+  text-align: center;
+  transform: rotate(-90deg);
 }
diff --git a/aleksis/core/static/print.css b/aleksis/core/static/print.css
index dad3abb59..1c3e9d486 100644
--- a/aleksis/core/static/print.css
+++ b/aleksis/core/static/print.css
@@ -1,133 +1,139 @@
 .sheet.infinite {
-    height: auto !important;
+  height: auto !important;
 }
 
 @page {
-    size: A4;
-    padding: 30mm;
-    margin: 0;
+  size: A4;
+  padding: 30mm;
+  margin: 0;
 }
 
 header {
-    display: block;
-    width: 190mm;
+  display: block;
+  width: 190mm;
 }
 
-
 #print-header {
-    display: block !important;
-    border-bottom: 1px solid;
-    margin-bottom: 0;
-    height: 22mm;
-    background: white;
+  display: block !important;
+  border-bottom: 1px solid;
+  margin-bottom: 0;
+  height: 22mm;
+  background: white;
 }
 
-header, main, footer {
-    margin: 0;
+header,
+main,
+footer {
+  margin: 0;
 }
 
 #print-header .col.right-align {
-    padding: 15px;
+  padding: 15px;
 }
 
 .sheet {
-    padding: 10mm;
+  padding: 10mm;
 }
 
-
-.header-space, .footer-space {
-    height: 0;
+.header-space,
+.footer-space {
+  height: 0;
 }
 
-.print-layout-table, .print-layout-td {
-    width: 190mm;
-    max-width: 190mm;
-    min-width: 190mm;
+.print-layout-table,
+.print-layout-td {
+  width: 190mm;
+  max-width: 190mm;
+  min-width: 190mm;
 }
 
 .print-layout-td {
-    padding: 0;
+  padding: 0;
 }
 
 .print-layout-table .no-border {
-    border: 0;
+  border: 0;
 }
 
-
 footer {
-    margin-top: 5mm;
-    text-align: center;
-    width: 190mm;
-
+  margin-top: 5mm;
+  text-align: center;
+  width: 190mm;
 }
 
-header .row, header .col {
-    padding: 0 !important;
-    margin: 0 !important;
+header .row,
+header .col {
+  padding: 0 !important;
+  margin: 0 !important;
 }
 
 #print-logo {
-    height: 22mm;
-    width: auto;
-    margin-block: 0;
-    padding: 2mm 2mm 2mm 0;
+  height: 22mm;
+  width: auto;
+  margin-block: 0;
+  padding: 2mm 2mm 2mm 0;
 }
 
 .page-break {
-    display: block;
-    text-align: center;
-    margin: auto;
-    margin-top: 20px;
-    margin-bottom: 20px;
-    width: 200px;
-    border-top: 1px dashed;
-    color: darkgrey;
-    page-break-after: always;
+  display: block;
+  text-align: center;
+  margin: auto;
+  margin-top: 20px;
+  margin-bottom: 20px;
+  width: 200px;
+  border-top: 1px dashed;
+  color: darkgrey;
+  page-break-after: always;
 }
 
 @media print {
-    .header-space {
-        height: 35mm;
-    }
+  .header-space {
+    height: 35mm;
+  }
 
-    .footer-space {
-        height: 20mm
-    }
+  .footer-space {
+    height: 20mm;
+  }
 
-    header, footer {
-        height: 22mm;
-    }
+  header,
+  footer {
+    height: 22mm;
+  }
 
-    header {
-        position: fixed;
-        top: 10mm;
-    }
+  header {
+    position: fixed;
+    top: 10mm;
+  }
 
-    footer {
-        position: fixed;
-        bottom: 0;
-    }
+  footer {
+    position: fixed;
+    bottom: 0;
+  }
 
-    .page-break {
-        border: white;
-    }
+  .page-break {
+    border: white;
+  }
 }
 
 /* Some stuff for tables */
 
-table.small-print, td.small-print, th.small-print {
-    font-size: 10pt;
+table.small-print,
+td.small-print,
+th.small-print {
+  font-size: 10pt;
 }
 
 tr {
-    border-bottom: 1px solid rgba(0, 0, 0, 0.3);
+  border-bottom: 1px solid rgba(0, 0, 0, 0.3);
 }
 
-td, th {
-    padding: 1px;
+td,
+th {
+  padding: 1px;
 }
 
-td.rotate, th.rotate {
-    text-align: center;
-    transform: rotate(-90deg);
+td.rotate,
+th.rotate {
+  text-align: center;
+  transform: rotate(-90deg);
 }
diff --git a/aleksis/core/static/print_landscape.css b/aleksis/core/static/print_landscape.css
index a348ddff6..746968664 100644
--- a/aleksis/core/static/print_landscape.css
+++ b/aleksis/core/static/print_landscape.css
@@ -1,19 +1,18 @@
 @page {
-    size: A4 landscape;
+  size: A4 landscape;
 }
 
 header {
-    width: 277mm;
+  width: 277mm;
 }
 
-
-.print-layout-table, .print-layout-td {
-    width: 277mm;
-    max-width: 277mm;
-    min-width: 277mm;
+.print-layout-table,
+.print-layout-td {
+  width: 277mm;
+  max-width: 277mm;
+  min-width: 277mm;
 }
 
-
 footer {
-    width: 277mm;
+  width: 277mm;
 }
diff --git a/aleksis/core/static/public/style.scss b/aleksis/core/static/public/style.scss
index 07f188121..998da7726 100644
--- a/aleksis/core/static/public/style.scss
+++ b/aleksis/core/static/public/style.scss
@@ -4,7 +4,8 @@
   background-color: $primary-color !important;
 }
 
-.primary-color-text, .primary-color-text a {
+.primary-color-text,
+.primary-color-text a {
   color: $primary-color !important;
 }
 
@@ -12,7 +13,8 @@
   background-color: $secondary-color !important;
 }
 
-.secondary-color-text, .secondary-color-text a {
+.secondary-color-text,
+.secondary-color-text a {
   color: $secondary-color !important;
 }
 
@@ -29,7 +31,7 @@ rect#background {
 }
 
 .success {
-  @extend .light-green, .lighten-3
+  @extend .light-green, .lighten-3;
 }
 
 .success-text {
@@ -64,16 +66,22 @@ body {
   flex-direction: column;
 }
 
-header, main, footer {
+header,
+main,
+footer {
   margin-left: 300px;
 }
 
-.without-menu header, .without-menu main, .without-menu footer {
+.without-menu header,
+.without-menu main,
+.without-menu footer {
   margin-left: 0;
 }
 
 @media only screen and (max-width: 992px) {
-  header, main, footer {
+  header,
+  main,
+  footer {
     margin-left: 0;
   }
 }
@@ -81,7 +89,10 @@ header, main, footer {
 .materialize-circle {
   @extend .circle;
 }
-.collection .collection-item.avatar > .materialize-circle > .materialize-circle {
+.collection
+  .collection-item.avatar
+  > .materialize-circle
+  > .materialize-circle {
   left: 0;
 }
 
@@ -98,7 +109,6 @@ header, main, footer {
   width: auto;
 }
 
-
 /********/
 /* MAIN */
 /********/
@@ -134,11 +144,18 @@ ul.sidenav li.logo > a:hover {
   background: none !important;
 }
 
-.sidenav .collapsible-body > ul:not(.collapsible) > li.active a > i, .sidenav.sidenav-fixed .collapsible-body > ul:not(.collapsible) > li.active a > i {
+.sidenav .collapsible-body > ul:not(.collapsible) > li.active a > i,
+.sidenav.sidenav-fixed
+  .collapsible-body
+  > ul:not(.collapsible)
+  > li.active
+  a
+  > i {
   color: #fff;
 }
 
-.sidenav .collapsible-body > ul:not(.collapsible) > li.active, .sidenav.sidenav-fixed .collapsible-body > ul:not(.collapsible) > li.active {
+.sidenav .collapsible-body > ul:not(.collapsible) > li.active,
+.sidenav.sidenav-fixed .collapsible-body > ul:not(.collapsible) > li.active {
   background-color: lighten($primary-color, 5%);
 }
 
@@ -161,8 +178,8 @@ ul.sidenav li.logo > a:hover {
   border-top: 1px solid rgba(0, 0, 0, 0.14);
   border-bottom: 1px solid rgba(0, 0, 0, 0.14);
 
-  -webkit-transition: margin .25s ease;
-  transition: margin .25s ease;
+  -webkit-transition: margin 0.25s ease;
+  transition: margin 0.25s ease;
 }
 
 .sidenav li.search .search-wrapper input#search {
@@ -215,7 +232,6 @@ div#search-results {
   right: 10px;
 }
 
-
 // Footer
 
 .footer-icon {
@@ -223,7 +239,6 @@ div#search-results {
   vertical-align: middle;
 }
 
-
 @media only screen and (min-width: 1384px) {
   .footer-row-large {
     display: flex;
@@ -280,10 +295,17 @@ h1 {
 
 h2 {
   font-weight: 300;
-  font-size: 3.0rem;
+  font-size: 3rem;
 }
 
-p, h1, h2, h3, h4, h5, h6, .card-title {
+p,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.card-title {
   overflow-wrap: break-word;
   hyphens: auto;
 }
@@ -294,7 +316,6 @@ ul.collection .collection-item .title {
   font-weight: bold;
 }
 
-
 // Forms
 
 form .row {
@@ -317,7 +338,7 @@ label.chips-checkbox {
   height: 32px;
   font-size: 13px;
   font-weight: 500;
-  color: rgba(0, 0, 0, .6);
+  color: rgba(0, 0, 0, 0.6);
   line-height: 32px;
   padding: 0 12px;
   border-radius: 16px;
@@ -403,24 +424,29 @@ span.badge .material-icons {
   font-size: 2rem;
 }
 
-.btn.primary, .btn-large.primary, .btn-small.primary {
+.btn.primary,
+.btn-large.primary,
+.btn-small.primary {
   background-color: rgba(0, 0, 0, 0.05) !important;
   color: black !important;
 }
 
-.btn.primary:hover, .btn-large.primary:hover, .btn-small.primary {
+.btn.primary:hover,
+.btn-large.primary:hover,
+.btn-small.primary {
   background-color: $primary-color !important;
   color: whitesmoke !important;
 }
 
-
 /* Table*/
 
 .table-container {
   overflow-x: auto;
 }
 
-table.striped > tbody > tr:nth-child(odd), table tr.striped, table tbody.striped tr {
+table.striped > tbody > tr:nth-child(odd),
+table tr.striped,
+table tbody.striped tr {
   background-color: rgba(208, 208, 208, 0.5);
 }
 
@@ -461,7 +487,9 @@ th.orderable.desc {
     font-size: 15px;
   }
 
-  header, main, footer {
+  header,
+  main,
+  footer {
     margin-left: 0;
   }
 
@@ -488,11 +516,14 @@ th.orderable.desc {
     padding: 15px;
   }
 
-  main, header {
+  main,
+  header {
     padding: 0;
   }
 
-  footer, footer .footer-copyright, footer .container {
+  footer,
+  footer .footer-copyright,
+  footer .container {
     background-color: white !important;
     color: black !important;
   }
@@ -501,7 +532,8 @@ th.orderable.desc {
     display: none;
   }
 
-  .footer-copyright, .footer-copyright .container {
+  .footer-copyright,
+  .footer-copyright .container {
     padding: 0 !important;
     margin: 0 !important;
   }
@@ -513,7 +545,8 @@ th.orderable.desc {
 
 // Alerts
 
-.alert ul, .alert p {
+.alert ul,
+.alert p {
   margin: 0;
 }
 
@@ -637,7 +670,6 @@ main figure.alert {
   margin-bottom: 5px;
 }
 
-
 /* Dashboard */
 
 .card-action-badge {
@@ -719,7 +751,6 @@ main figure.alert {
   }
 }
 
-
 .dashboard-cards .card {
   display: inline-block;
   overflow: visible;
@@ -755,14 +786,15 @@ main figure.alert {
 }
 
 /* Tabs with icons */
-.tabs-icons, .tabs-icons .tab, .tabs-icons a {
+.tabs-icons,
+.tabs-icons .tab,
+.tabs-icons a {
   height: 72px;
 }
 
 .tabs-icons .tab {
   display: inline-flex;
   flex-direction: column;
-
 }
 
 .tabs-icons .tab a {
@@ -798,7 +830,8 @@ $person-logo-size: 20vh;
   }
 }
 
-.clip-circle.no-image, .clip-circle.no-image > i.material-icons {
+.clip-circle.no-image,
+.clip-circle.no-image > i.material-icons {
   font-size: calc(#{$person-logo-size} * 0.5);
   color: #6f6f6f;
   background: #f2f2f2;
@@ -817,8 +850,8 @@ $person-logo-size: 20vh;
   justify-content: space-between;
   padding: 0 1rem;
   > a {
-    position: static!important;
-    transform: none!important;
+    position: static !important;
+    transform: none !important;
   }
   & .nav-spacer {
     width: 60px;
@@ -840,16 +873,17 @@ $person-logo-size: 20vh;
 
 .navbar-dropdown-trigger .clip-circle {
   margin: auto;
-  width: $navbar-height*0.75;
-  height: $navbar-height*0.75;
+  width: $navbar-height * 0.75;
+  height: $navbar-height * 0.75;
   cursor: pointer;
 
-  &.no-image, &.no-image > i.material-icons {
+  &.no-image,
+  &.no-image > i.material-icons {
     font-size: calc(#{$navbar-height} * 0.75 * 0.5);
     color: #6f6f6f;
     background: #f2f2f2;
-    line-height: $navbar-height*0.75;
-    width: $navbar-height*0.75;
+    line-height: $navbar-height * 0.75;
+    width: $navbar-height * 0.75;
     cursor: pointer;
   }
 }
@@ -878,8 +912,8 @@ a.new-notification {
   background-color: lighten($primary-color, 30%);
   z-index: -1;
   box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14) inset,
-  0 1px 10px 0 rgba(0, 0, 0, 0.12) inset,
-  0 2px 4px -1px rgba(0, 0, 0, 0.3) inset;
+    0 1px 10px 0 rgba(0, 0, 0, 0.12) inset,
+    0 2px 4px -1px rgba(0, 0, 0, 0.3) inset;
 }
 
 .person-buttons {
@@ -954,7 +988,6 @@ a.new-notification {
   height: 20vh;
 }
 
-
 .application-circle img {
   @extend .application-circle;
   object-fit: cover;
@@ -964,7 +997,8 @@ svg.iconify {
   @extend i;
 }
 
-.btn .iconify.material-icons, .btn-flat .iconify.material-icons{
+.btn .iconify.material-icons,
+.btn-flat .iconify.material-icons {
   height: $button-height;
 }
 
@@ -992,7 +1026,8 @@ p.ical-description {
   font-weight: 300;
 }
 
-.table-circle, .table-circle .materialize-circle {
+.table-circle,
+.table-circle .materialize-circle {
   height: 4em;
   width: 4em;
 }
diff --git a/aleksis/core/static/public/theme.scss b/aleksis/core/static/public/theme.scss
index 1b38e9bda..3850e4c17 100644
--- a/aleksis/core/static/public/theme.scss
+++ b/aleksis/core/static/public/theme.scss
@@ -29,28 +29,30 @@
 //  23. Collections
 //  24. Progress Bar
 
-
-
 // 1. Colors
 // ==========================================================================
 
-$primary-color: adjust-color(get-colour(get-preference(theme, primary)), $alpha: 1);
+$primary-color: adjust-color(
+  get-colour(get-preference(theme, primary)),
+  $alpha: 1
+);
 $primary-color-light: lighten($primary-color, 15%) !default;
 $primary-color-dark: darken($primary-color, 15%) !default;
 
-$secondary-color: adjust-color(get-colour(get-preference(theme, secondary)), $alpha: 1);
+$secondary-color: adjust-color(
+  get-colour(get-preference(theme, secondary)),
+  $alpha: 1
+);
 $success-color: color("green", "base") !default;
 $error-color: color("red", "base") !default;
 $link-color: color("light-blue", "darken-1") !default;
 
-
 // 2. Badges
 // ==========================================================================
 
 $badge-bg-color: $secondary-color !default;
 $badge-height: 22px !default;
 
-
 // 3. Buttons
 // ==========================================================================
 
@@ -64,12 +66,15 @@ $button-padding: 0 16px !default;
 $button-radius: 2px !default;
 
 // Disabled styles
-$button-disabled-background: #DFDFDF !default;
-$button-disabled-color: #9F9F9F !default;
+$button-disabled-background: #dfdfdf !default;
+$button-disabled-color: #9f9f9f !default;
 
 // Raised buttons
 $button-raised-background: $secondary-color !default;
-$button-raised-background-hover: lighten($button-raised-background, 5%) !default;
+$button-raised-background-hover: lighten(
+  $button-raised-background,
+  5%
+) !default;
 $button-raised-color: #fff !default;
 
 // Large buttons
@@ -81,8 +86,8 @@ $button-floating-large-size: 56px !default;
 // Small buttons
 $button-small-font-size: 13px !default;
 $button-small-icon-font-size: 1.2rem !default;
-$button-small-height: $button-height * .9 !default;
-$button-floating-small-size: $button-height * .9 !default;
+$button-small-height: $button-height * 0.9 !default;
+$button-floating-small-size: $button-height * 0.9 !default;
 
 // Flat buttons
 $button-flat-color: #343434 !default;
@@ -95,7 +100,6 @@ $button-floating-color: #fff !default;
 $button-floating-size: 40px !default;
 $button-floating-radius: 50% !default;
 
-
 // 4. Cards
 // ==========================================================================
 
@@ -104,7 +108,6 @@ $card-bg-color: #fff !default;
 $card-link-color: $primary-color !default;
 $card-link-color-light: lighten($card-link-color, 20%) !default;
 
-
 // 5. Carousel
 // ==========================================================================
 
@@ -112,7 +115,6 @@ $carousel-height: 400px !default;
 $carousel-item-height: $carousel-height / 2 !default;
 $carousel-item-width: $carousel-item-height !default;
 
-
 // 6. Collapsible
 // ==========================================================================
 
@@ -121,7 +123,6 @@ $collapsible-line-height: $collapsible-height !default;
 $collapsible-header-color: #fff !default;
 $collapsible-border-color: #ddd !default;
 
-
 // 7. Chips
 // ==========================================================================
 
@@ -130,26 +131,30 @@ $chip-border-color: #9e9e9e !default;
 $chip-selected-color: $primary-color !default;
 $chip-margin: 5px !default;
 
-
 // 8. Date + Time Picker
 // ==========================================================================
 
 $datepicker-display-font-size: 2.8rem;
 $datepicker-calendar-header-color: #999;
-$datepicker-weekday-color: rgba(0, 0, 0, .87) !default;
+$datepicker-weekday-color: rgba(0, 0, 0, 0.87) !default;
 $datepicker-weekday-bg: darken($secondary-color, 7%) !default;
 $datepicker-date-bg: $secondary-color !default;
-$datepicker-year: rgba(255, 255, 255, .7) !default;
-$datepicker-focus: rgba(0,0,0, .05) !default;
+$datepicker-year: rgba(255, 255, 255, 0.7) !default;
+$datepicker-focus: rgba(0, 0, 0, 0.05) !default;
 $datepicker-selected: $secondary-color !default;
-$datepicker-selected-outfocus: desaturate(lighten($secondary-color, 35%), 15%) !default;
-$datepicker-day-focus: transparentize(desaturate($secondary-color, 5%), .75) !default;
-$datepicker-disabled-day-color: rgba(0, 0, 0, .3) !default;
-
-$timepicker-clock-color: rgba(0, 0, 0, .87) !default;
+$datepicker-selected-outfocus: desaturate(
+  lighten($secondary-color, 35%),
+  15%
+) !default;
+$datepicker-day-focus: transparentize(
+  desaturate($secondary-color, 5%),
+  0.75
+) !default;
+$datepicker-disabled-day-color: rgba(0, 0, 0, 0.3) !default;
+
+$timepicker-clock-color: rgba(0, 0, 0, 0.87) !default;
 $timepicker-clock-plate-bg: #eee !default;
 
-
 // 9. Dropdown
 // ==========================================================================
 
@@ -158,7 +163,6 @@ $dropdown-hover-bg-color: #eee !default;
 $dropdown-color: $secondary-color !default;
 $dropdown-item-height: 50px !default;
 
-
 // 10. Forms
 // ==========================================================================
 
@@ -174,8 +178,8 @@ $input-font-size: 16px !default;
 $input-margin-bottom: 8px;
 $input-margin: 0 0 $input-margin-bottom 0 !default;
 $input-padding: 0 !default;
-$label-font-size: .8rem !default;
-$input-disabled-color: rgba(0,0,0, .42) !default;
+$label-font-size: 0.8rem !default;
+$input-disabled-color: rgba(0, 0, 0, 0.42) !default;
 $input-disabled-solid-color: #949494 !default;
 $input-disabled-border: 1px dotted $input-disabled-color !default;
 $input-invalid-border: 1px solid $input-error-color !default;
@@ -194,23 +198,25 @@ $track-height: 3px !default;
 
 // Select
 $select-border: 1px solid #f2f2f2 !default;
-$select-background: rgba(255, 255, 255, 0.90) !default;
+$select-background: rgba(255, 255, 255, 0.9) !default;
 $select-focus: 1px solid lighten($secondary-color, 47%) !default;
-$select-option-hover: rgba(0,0,0,.08) !default;
-$select-option-focus: rgba(0,0,0,.08) !default;
-$select-option-selected: rgba(0,0,0,.03) !default;
+$select-option-hover: rgba(0, 0, 0, 0.08) !default;
+$select-option-focus: rgba(0, 0, 0, 0.08) !default;
+$select-option-selected: rgba(0, 0, 0, 0.03) !default;
 $select-padding: 5px !default;
 $select-radius: 2px !default;
-$select-disabled-color: rgba(0,0,0,.3) !default;
+$select-disabled-color: rgba(0, 0, 0, 0.3) !default;
 
 // Switches
 $switch-bg-color: $secondary-color !default;
-$switch-checked-lever-bg: desaturate(lighten($switch-bg-color, 25%), 25%) !default;
-$switch-unchecked-bg: #F1F1F1 !default;
-$switch-unchecked-lever-bg: rgba(0,0,0,.38) !default;
+$switch-checked-lever-bg: desaturate(
+  lighten($switch-bg-color, 25%),
+  25%
+) !default;
+$switch-unchecked-bg: #f1f1f1 !default;
+$switch-unchecked-lever-bg: rgba(0, 0, 0, 0.38) !default;
 $switch-radius: 15px !default;
 
-
 // 11. Global
 // ==========================================================================
 
@@ -229,15 +235,13 @@ $small-and-down: "only screen and (max-width : #{$small-screen})" !default;
 $medium-and-down: "only screen and (max-width : #{$medium-screen})" !default;
 $medium-only: "only screen and (min-width : #{$small-screen-up}) and (max-width : #{$medium-screen})" !default;
 
-
 // 12. Grid
 // ==========================================================================
 
 $num-cols: 12 !default;
 $gutter-width: 1.5rem !default;
 $element-top-margin: $gutter-width/3 !default;
-$element-bottom-margin: ($gutter-width*2)/3 !default;
-
+$element-bottom-margin: ($gutter-width * 2)/3 !default;
 
 // 13. Navigation Bar
 // ==========================================================================
@@ -255,27 +259,24 @@ $navbar-brand-font-size: 2.1rem !default;
 
 $sidenav-width: 300px !default;
 $sidenav-font-size: 14px !default;
-$sidenav-font-color: rgba(0,0,0,.87) !default;
+$sidenav-font-color: rgba(0, 0, 0, 0.87) !default;
 $sidenav-bg-color: #fff !default;
 $sidenav-padding: 16px !default;
 $sidenav-item-height: 48px !default;
 $sidenav-line-height: $sidenav-item-height !default;
 
-
 // 15. Photo Slider
 // ==========================================================================
 
-$slider-bg-color: color('grey', 'base') !default;
-$slider-bg-color-light: color('grey', 'lighten-2') !default;
-$slider-indicator-color: color('green', 'base') !default;
-
+$slider-bg-color: color("grey", "base") !default;
+$slider-bg-color-light: color("grey", "lighten-2") !default;
+$slider-indicator-color: color("green", "base") !default;
 
 // 16. Spinners | Loaders
 // ==========================================================================
 
 $spinner-default-color: $secondary-color !default;
 
-
 // 17. Tabs
 // ==========================================================================
 
@@ -283,14 +284,12 @@ $tabs-underline-color: $primary-color-light !default;
 $tabs-text-color: $primary-color !default;
 $tabs-bg-color: #fff !default;
 
-
 // 18. Tables
 // ==========================================================================
 
-$table-border-color: rgba(0,0,0,.12) !default;
+$table-border-color: rgba(0, 0, 0, 0.12) !default;
 $table-striped-color: rgba(242, 242, 242, 0.5) !default;
 
-
 // 19. Toasts
 // ==========================================================================
 
@@ -299,11 +298,11 @@ $toast-color: #323232 !default;
 $toast-text-color: #fff !default;
 $toast-action-color: #eeff41;
 
-
 // 20. Typography
 // ==========================================================================
 
-$font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif !default;
+$font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans,
+  Ubuntu, Cantarell, "Helvetica Neue", sans-serif !default;
 $off-black: rgba(0, 0, 0, 0.87) !default;
 // Header Styles
 $h1-fontsize: 4.2rem !default;
@@ -313,24 +312,21 @@ $h4-fontsize: 2.28rem !default;
 $h5-fontsize: 1.64rem !default;
 $h6-fontsize: 1.15rem !default;
 
-
 // 21. Footer
 // ==========================================================================
 
 $footer-font-color: #fff !default;
 $footer-bg-color: $primary-color !default;
-$footer-copyright-font-color: rgba(255,255,255,.8) !default;
-$footer-copyright-bg-color: rgba(51,51,51,.08) !default;
-
+$footer-copyright-font-color: rgba(255, 255, 255, 0.8) !default;
+$footer-copyright-bg-color: rgba(51, 51, 51, 0.08) !default;
 
 // 22. Flow Text
 // ==========================================================================
 
-$range : $large-screen - $small-screen !default;
+$range: $large-screen - $small-screen !default;
 $intervals: 20 !default;
 $interval-size: $range / $intervals !default;
 
-
 // 23. Collections
 // ==========================================================================
 
@@ -342,7 +338,6 @@ $collection-hover-bg-color: #ddd !default;
 $collection-link-color: $secondary-color !default;
 $collection-line-height: 1.5rem !default;
 
-
 // 24. Progress Bar
 // ==========================================================================
 
diff --git a/aleksis/core/templates/core/vue_base.html b/aleksis/core/templates/core/vue_base.html
index 3d8f6a7bf..07b062778 100644
--- a/aleksis/core/templates/core/vue_base.html
+++ b/aleksis/core/templates/core/vue_base.html
@@ -19,17 +19,12 @@
   </title>
 
   {# CSS #}
-  {# FIXME ↓ #}
-  {#  {% include_css "material-design-icons" %}#}
   {% include_css "Roboto100" %}
   {% include_css "Roboto300" %}
   {% include_css "Roboto400" %}
   {% include_css "Roboto500" %}
   {% include_css "Roboto700" %}
   {% include_css "Roboto900" %}
-  {#  <link rel="stylesheet" href="{% sass_src 'public/style.scss' %}">#}
-
-  <!-- FIXME: Find a way to use SCSS!!! -->
 
   {# Add JS URL resolver #}
   <script src="{% url "js_reverse" %}" type="text/javascript"></script>
@@ -61,9 +56,6 @@
   <script type="text/javascript" src="{% url 'config.js' %}"></script>
   {% include_js "iconify" %}
 
-  {# Include jQuery early to provide $(document).ready #}
-  {% include_js "jQuery" %}
-
   {% block extra_head %}{% endblock %}
 </head>
 <body {% if no_menu %}class="without-menu"{% endif %}>
diff --git a/aleksis/core/templates/templated_email/email.css b/aleksis/core/templates/templated_email/email.css
index 8cd112624..465da3dd0 100644
--- a/aleksis/core/templates/templated_email/email.css
+++ b/aleksis/core/templates/templated_email/email.css
@@ -1,41 +1,45 @@
 body {
-    line-height: 1.5;
-    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
-    font-weight: normal;
-    color: rgba(0, 0, 0, 0.87);
-    display: flex;
-    justify-content: center;
-    align-items: center;
+  line-height: 1.5;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
+    Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
+  font-weight: normal;
+  color: rgba(0, 0, 0, 0.87);
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 
-table, tr {
-    width: 100%;
+table,
+tr {
+  width: 100%;
 }
 
 .main {
-    max-width: 700px;
-    box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2);
-    -webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2);
-    -webkit-transition: -webkit-box-shadow .25s;
-    transition: -webkit-box-shadow .25s;
-    transition: box-shadow .25s;
-    transition: box-shadow .25s, -webkit-box-shadow .25s;
-    border-radius: 2px;
-    background-color: #fff;
-    margin: 30px;
-    padding: 20px;
+  max-width: 700px;
+  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14),
+    0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2);
+  -webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14),
+    0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2);
+  -webkit-transition: -webkit-box-shadow 0.25s;
+  transition: -webkit-box-shadow 0.25s;
+  transition: box-shadow 0.25s;
+  transition: box-shadow 0.25s, -webkit-box-shadow 0.25s;
+  border-radius: 2px;
+  background-color: #fff;
+  margin: 30px;
+  padding: 20px;
 }
 
 .first th {
-    border-bottom: 1px solid;
+  border-bottom: 1px solid;
 }
 
-
-td, th {
-    padding-left: 5px;
-    padding-right: 5px;
+td,
+th {
+  padding-left: 5px;
+  padding-right: 5px;
 }
 
 .align-center {
-    text-align: center;
+  text-align: center;
 }
diff --git a/aleksis/core/webpack.config.js b/aleksis/core/webpack.config.js
index f33cc6025..8f327cbe5 100644
--- a/aleksis/core/webpack.config.js
+++ b/aleksis/core/webpack.config.js
@@ -1,50 +1,58 @@
-const fs = require('fs');
-const path = require('path');
-const webpack = require('webpack');
-const BundleTracker = require('webpack-bundle-tracker');
-const { VueLoaderPlugin } = require('vue-loader');
+const fs = require("fs");
+const path = require("path");
+const webpack = require("webpack");
+const BundleTracker = require("webpack-bundle-tracker");
+const { VueLoaderPlugin } = require("vue-loader");
+const ESLintPlugin = require("eslint-webpack-plugin");
+const StyleLintPlugin = require("stylelint-webpack-plugin");
 
 module.exports = {
   context: __dirname,
-  entry: JSON.parse(fs.readFileSync('./webpack-entrypoints.json')),
+  entry: JSON.parse(fs.readFileSync("./webpack-entrypoints.json")),
   output: {
-    path: path.resolve('./webpack_bundles/'),
+    path: path.resolve("./webpack_bundles/"),
     filename: "[name]-[hash].js",
     chunkFilename: "[id]-[chunkhash].js",
   },
   plugins: [
-    new BundleTracker({filename: './webpack-stats.json'}),
+    new BundleTracker({ filename: "./webpack-stats.json" }),
     new VueLoaderPlugin(),
+    new ESLintPlugin({
+      extensions: ["js", "vue"],
+    }),
+    new StyleLintPlugin({
+      files: ["assets/**/*.{vue,htm,html,css,sss,less,scss,sass}"],
+    }),
   ],
   module: {
     rules: [
       {
         test: /\.vue$/,
         use: {
-          loader: 'vue-loader',
+          loader: "vue-loader",
           options: {
             transpileOptions: {
               transforms: {
-                dangerousTaggedTemplateString: true
-              }
-            }
-          }
+                dangerousTaggedTemplateString: true,
+              },
+            },
+          },
         },
       },
       {
         test: /\.(css)$/,
-        use: ['vue-style-loader', 'css-loader'],
+        use: ["vue-style-loader", "css-loader"],
       },
       {
         test: /\.scss$/,
         use: [
-          'vue-style-loader',
-          'css-loader',
+          "vue-style-loader",
+          "css-loader",
           {
-            loader: 'sass-loader',
+            loader: "sass-loader",
             options: {
               sassOptions: {
-                indentedSyntax: false
+                indentedSyntax: false,
               },
             },
           },
@@ -53,7 +61,7 @@ module.exports = {
       {
         test: /\.(graphql|gql)$/,
         exclude: /node_modules/,
-        loader: 'graphql-tag/loader',
+        loader: "graphql-tag/loader",
       },
     ],
   },
@@ -75,15 +83,15 @@ module.exports = {
 
             // npm package names are URL-safe, but some servers don't like @ symbols
             return `npm.${packageName.replace("@", "")}`;
-          }
-        }
-      }
-    }
+          },
+        },
+      },
+    },
   },
   resolve: {
-    modules: [path.resolve('./node_modules')],
+    modules: [path.resolve("./node_modules")],
     alias: {
-      'vue$': 'vue/dist/vue.esm.js'
-    }
+      vue$: "vue/dist/vue.esm.js",
+    },
   },
-}
+};
-- 
GitLab