diff --git a/.eslintrc.js b/.eslintrc.js index 15b0fbe2891c2f685e867206fc6954620237b5d6..4c2043012828bd16438eb4f36472ad48460eb6e4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,9 +1,15 @@ module.exports = { extends: [ - 'plugin:vue/strongly-recommended', + "eslint:recommended", + "plugin:vue/strongly-recommended", + "prettier", ], rules: { - 'vue/no-unused-vars': 'off', - 'vue/multi-word-component-names': 'off' - } -} + "vue/no-unused-vars": "off", + "vue/multi-word-component-names": "off", + }, + env: { + browser: true, + node: true, + }, +}; diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 564449be1717a97282b53b2d94f9f234b34261c2..cf02f39e36aa6fa715f8e3c73c8069433c44f497 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/.prettierignore b/.prettierignore new file mode 100644 index 0000000000000000000000000000000000000000..81a9d0fcb7eb41ce3cccfe351836df790bd2203c --- /dev/null +++ b/.prettierignore @@ -0,0 +1,87 @@ +# Byte-compiled / optimized / DLL files +*$py.class +*.py[cod] +__pycache__/ + +# Distribution / packaging +*.egg +*.egg-info/ +.Python +.eggs/ +.installed.cfg +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ + +# Installer logs +pip-delete-this-directory.txt +pip-log.txt + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# pyenv +.python-version + +# Environments +.env +.venv +ENV/ +env/ +venv/ + +# Editors +*~ +DEADJOE +\#*# + +# IntelliJ +.idea +.idea/ + +# Database +db.sqlite3 + +# Sphinx +docs/_build/ + +# TeX +*.aux + +# Generated files +/node_modules/ +/static/ +/whoosh_index/ +poetry.lock + +.coverage +.mypy_cache/ +.tox/ +htmlcov/ +maintenance_mode_state.txt +media/ +package-lock.json +yarn.lock + +# VSCode +.vscode/ +.history/ +*.code-workspace + +/cache + +# Add HTML files to avoid problems with unsupported Django templates +*.html diff --git a/.stylelintrc.json b/.stylelintrc.json index 40db42c6689bd157e91cec65fda28693350b6332..2e8ff5864a48be6a22bd1742c2317556a8ec9419 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,3 +1,3 @@ { - "extends": "stylelint-config-standard" + "extends": ["stylelint-config-standard", "stylelint-config-prettier"] } diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 116019ee190b6e294059cf14c3b15135bab57864..c7bd26b7e8e94374e84f1d9c3252832610f6c2c5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -31,6 +31,9 @@ Fixed * Sometimes the PDF files were not generated correctly and images were displayed only partially. * Error message in permission form was misleading. +* Personal invites did not work +* Invite Person view threw an error when personal invites existed +* Detailed information for done Celery tasks weren't saved. Removed ~~~~~~~ diff --git a/aleksis/core/assets/app.js b/aleksis/core/assets/app.js index 32d4f1b0ed34fea9327b7b1c83257b15009eabd1..148612e2951728137b26e6a93cd98c0cf1112129 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 d636a056cab92f3464a602d9a02af923197c2a35..f30adbd0bf8f02016216d9285382f7e68e30649c 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 2897d3a3057c8e11211bc16149b137aa573e1021..d9b0d0b707e279500701350affb843388161e1ba 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 2a4cb17295835f5d3d1eb6f310a935eb4c2789be..4c79f4d51a36123f4e91f4e187e594d83387e1d3 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 30fdbe9596487061d962c82af968f659fa524e5f..1c94ce9ef82ef7c0005bceab7d3280383a641f73 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 2035cbd69d664c59d63ab95cd39571b27842d74e..3659f1f2794382722b9e7671edd831c9bc4a0911 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 cb42dc17af31525d437594e268139401a2186a00..9e057ef7f4093f01d072bb88b6e99f2cb13841f8 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 a457b4800f3e2fa9cc1c5927c430cea2161fb913..66e731dfe7d3a47498d4356b080dc1839898d1c1 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 03c33d7313dc3a51f237ac0cea45d0e220fae1d6..08734f663a4603c458f45d48b11159f6300cccea 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" diff --git a/aleksis/core/assets/messages.json b/aleksis/core/assets/messages.json index bca01a4fd0bf9a6e4a10c504e2812809f464793f..3e1eb6879b650779e04d6c313f3c9868b592c6d5 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." }, @@ -56,4 +56,3 @@ } } } - diff --git a/aleksis/core/assets/util.js b/aleksis/core/assets/util.js index 1b5041b216cdae06868440b14b88f3d69acee914..a16f92bed1fc90e1930b89055278616419cf9c3b 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/forms.py b/aleksis/core/forms.py index 05ebef2f4f3dd17124a4449ebe06b6622c1ad1be..136c6d38f1bf7b143ecf631077f48b75c2149957 100644 --- a/aleksis/core/forms.py +++ b/aleksis/core/forms.py @@ -639,8 +639,12 @@ class AccountRegisterForm(SignupForm, ExtensibleForm): for field in Person._meta.get_fields(): if field.name in self.cleaned_data: data[field.name] = self.cleaned_data[field.name] - if not Person.objects.filter(email=data["email"]): - _person, created = Person.objects.update_or_create(user=user, **data) + person_qs = Person.objects.filter(email=data["email"]) + if not person_qs.exists(): + if get_site_preferences()["account__auto_create_person"]: + Person.objects.create(user=user, **data) + else: + person_qs.update(user=user, **data) self.custom_signup(request, user) setup_user_email(request, user, []) return user diff --git a/aleksis/core/locale/ar/LC_MESSAGES/django.po b/aleksis/core/locale/ar/LC_MESSAGES/django.po index df6153f7196d2535a19143cb11bfa43a0f831953..ce6e7cbbf7d64d4beb966c224212bbda2b32af2f 100644 --- a/aleksis/core/locale/ar/LC_MESSAGES/django.po +++ b/aleksis/core/locale/ar/LC_MESSAGES/django.po @@ -2052,7 +2052,7 @@ msgstr "" #: aleksis/core/templates/core/pages/system_status.html:24 msgid "" "\n" -" Only admin and visitors from internal IPs can access thesite.\n" +" Only admin and visitors from internal IPs can access the site.\n" " " msgstr "" diff --git a/aleksis/core/locale/de_DE/LC_MESSAGES/django.po b/aleksis/core/locale/de_DE/LC_MESSAGES/django.po index 7f2dcc874f2a27ea8cf2b0d8b7fd681e17e419f8..47480319a7caddf203af0a5823a09753dcb8814c 100644 --- a/aleksis/core/locale/de_DE/LC_MESSAGES/django.po +++ b/aleksis/core/locale/de_DE/LC_MESSAGES/django.po @@ -2308,7 +2308,7 @@ msgstr "Wartungsmodus aktiviert" msgid "" "\n" " Only admin and visitors from internal IPs can access " -"thesite.\n" +"the site.\n" " " msgstr "" "\n" diff --git a/aleksis/core/locale/fr/LC_MESSAGES/django.po b/aleksis/core/locale/fr/LC_MESSAGES/django.po index b57a1588c4fb9a13d74200af11aa150a0f7b2c7d..8bc699a6898347499a588ac071ded207c301d4c3 100644 --- a/aleksis/core/locale/fr/LC_MESSAGES/django.po +++ b/aleksis/core/locale/fr/LC_MESSAGES/django.po @@ -2130,7 +2130,7 @@ msgstr "" #: aleksis/core/templates/core/pages/system_status.html:24 msgid "" "\n" -" Only admin and visitors from internal IPs can access thesite.\n" +" Only admin and visitors from internal IPs can access the site.\n" " " msgstr "" diff --git a/aleksis/core/locale/la/LC_MESSAGES/django.po b/aleksis/core/locale/la/LC_MESSAGES/django.po index b75d5e04731de1445f993badba1509a713f9e474..f75c1ff3027200fcc1f340efe6a34b17cb4e3529 100644 --- a/aleksis/core/locale/la/LC_MESSAGES/django.po +++ b/aleksis/core/locale/la/LC_MESSAGES/django.po @@ -2238,7 +2238,7 @@ msgstr "" #: aleksis/core/templates/core/pages/system_status.html:24 msgid "" "\n" -" Only admin and visitors from internal IPs can access thesite.\n" +" Only admin and visitors from internal IPs can access the site.\n" " " msgstr "" diff --git a/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po b/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po index c5d7f44167fc0d9207afcc857d56d3fecb23e834..a4ef0700a3b00bab188af924342dd605c6624b21 100644 --- a/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po +++ b/aleksis/core/locale/nb_NO/LC_MESSAGES/django.po @@ -2051,7 +2051,7 @@ msgstr "" #: aleksis/core/templates/core/pages/system_status.html:24 msgid "" "\n" -" Only admin and visitors from internal IPs can access thesite.\n" +" Only admin and visitors from internal IPs can access the site.\n" " " msgstr "" diff --git a/aleksis/core/locale/ru/LC_MESSAGES/django.po b/aleksis/core/locale/ru/LC_MESSAGES/django.po index 722b473df4cad72ab8b7988d21325946bc6db3ee..378e7a1a676224c921dfb2f19c2d4cb5507c420f 100644 --- a/aleksis/core/locale/ru/LC_MESSAGES/django.po +++ b/aleksis/core/locale/ru/LC_MESSAGES/django.po @@ -2248,7 +2248,7 @@ msgstr "Включен режим обÑлуживаниÑ" msgid "" "\n" " Only admin and visitors from internal IPs can access " -"thesite.\n" +"the site.\n" " " msgstr "" "\n" diff --git a/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po b/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po index c6ea389cc19f7b8da9cd26512cca741c38b41311..aa34c1560b1444e80ac7c9fb86995b007142332e 100644 --- a/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po +++ b/aleksis/core/locale/tr_TR/LC_MESSAGES/django.po @@ -2051,7 +2051,7 @@ msgstr "" #: aleksis/core/templates/core/pages/system_status.html:24 msgid "" "\n" -" Only admin and visitors from internal IPs can access thesite.\n" +" Only admin and visitors from internal IPs can access the site.\n" " " msgstr "" diff --git a/aleksis/core/locale/uk/LC_MESSAGES/django.po b/aleksis/core/locale/uk/LC_MESSAGES/django.po index 58a52db0bbb5a0611820645658142a54de9b6e89..a5987d90d6bc2c3b1b54920ea0601d4f148eec23 100644 --- a/aleksis/core/locale/uk/LC_MESSAGES/django.po +++ b/aleksis/core/locale/uk/LC_MESSAGES/django.po @@ -2282,7 +2282,7 @@ msgstr "Ðктивований режим обÑлуговуваннÑ" msgid "" "\n" " Only admin and visitors from internal IPs can access " -"thesite.\n" +"the site.\n" " " msgstr "" "\n" diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index 0440d3424e77315fba6329b99ec91f7dd5e86d07..587dab4ee641ceda79441ae5ddcb910ce184771f 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -580,6 +580,14 @@ YARN_INSTALLED_APPS = [ "webpack-bundle-tracker@^1.6.0", "webpack-cli@^4.10.0", "vue-i18n@8", + "eslint@^8.26.0", + "eslint-plugin-vue@^9.7.0", + "eslint-webpack-plugin@^3.2.0", + "eslint-config-prettier@^8.5.0", + "stylelint@^14.14.0", + "stylelint-config-standard@^29.0.0", + "stylelint-webpack-plugin@^3.3.0", + "stylelint-config-prettier@^9.0.3", ] merge_app_settings("YARN_INSTALLED_APPS", YARN_INSTALLED_APPS, True) @@ -741,6 +749,7 @@ CELERY_BROKER_URL = _settings.get("celery.broker", REDIS_URL) CELERY_RESULT_BACKEND = "django-db" CELERY_CACHE_BACKEND = "django-cache" CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" +CELERY_RESULT_EXTENDED = True if _settings.get("celery.email", False): EMAIL_BACKEND = "djcelery_email.backends.CeleryEmailBackend" diff --git a/aleksis/core/static/js/copy_button.js b/aleksis/core/static/js/copy_button.js index 554f6230e4f9c8b9bb012875e31da25c4f61c2c6..c7f53e61bf5fa91a13fd24be0b01832755e955d4 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 0cc90de60305497a682d219b121e77550d273d1d..b6e441191c118b4956e3f54cab919d9caa8fde25 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 844496346e451a89814ae90194db57fb67a72434..48dab95651703837a071ee39bcc3f9682d071c9b 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 3a4794bad9881ebe502ea7786b8c0d2a740e65f0..0d23769c1c68114c638a8daf7217b2bc72d8948e 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 b735e2bb53d4a3c6a8f5b1edc9a9bb307214e1b4..c8ebd1383f4e80f5ea93d4b59671974a5fdf3dac 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 cddf911b5f50be2217075d2c0f34191adab6f72e..105f4c13d6632224a6a88ba2a1d4047255171439 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 8a97577e93d276cd62c69ead4c65b448a7631d49..dc554ec8f37cd6c8e649f5b1d72d503ad93a887f 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 fd121dbdfafe20c1c60ef2c793cd1f13b2a7573e..1841829ecca8de19f51aa154732e90d309ec9aa4 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 16382ec00c5dadb22c63060a227451377202d363..8fa870824dd46202731afd8dd439b15aad1da4c5 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 f0e6536b4d835b67ab0d519de13c561953be4ea5..dfde8908dbe44e0bfa21039364455ed4b9154906 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 dad3abb5967e84b014769240ce000d33ced9f014..1c3e9d486a27e913671a453219c60b70b8778462 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 a348ddff6268f56a1f4fa82eac240d6c9823e14c..746968664ee7ac8e04643e97e6964cfc57796eca 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 07f1881216448b3c82fb605af72b214004bf2f03..998da7726f3758310068225b7d929be51975eb14 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 1b38e9bda22c7b63601a4a227d58981e2e3a8d45..3850e4c17da80967b7729bd5feebf937ee3cee5e 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/tables.py b/aleksis/core/tables.py index a58c3fc443b3ad66c8df31109cbb6276192518ef..c912d13477c607e3374b3c8b4619338a8cb8a6a0 100644 --- a/aleksis/core/tables.py +++ b/aleksis/core/tables.py @@ -156,7 +156,7 @@ class PersonColumn(tables.Column): """Returns person object from given id.""" def render(self, value): - return Person.objects.get(user__id=value) + return Person.objects.get(pk=value) class InvitationCodeColumn(tables.Column): diff --git a/aleksis/core/templates/core/pages/system_status.html b/aleksis/core/templates/core/pages/system_status.html index 4eee117807c2b2a45a1271e1f4aa9673189d105d..7f0e7bff0840029b500620662cd4681ac6bddded 100644 --- a/aleksis/core/templates/core/pages/system_status.html +++ b/aleksis/core/templates/core/pages/system_status.html @@ -22,7 +22,7 @@ <p class="flow-text">{% blocktrans %}Maintenance mode enabled{% endblocktrans %}</p> <p class="grey-text"> {% blocktrans %} - Only admin and visitors from internal IPs can access thesite. + Only admin and visitors from internal IPs can access the site. {% endblocktrans %} </p> </div> diff --git a/aleksis/core/templates/core/vue_base.html b/aleksis/core/templates/core/vue_base.html index 3d8f6a7bf10553c6aed4d2dde2fbe1740b7651fc..07b06277856d27283a8612882c0d287f40590f1f 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 8cd112624c0c0b78f663027841895f4e07ce608b..465da3dd073bf681eb3146a077ea17d46f643fca 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 f33cc60252c0c11bfeacb4c304db7c396a9530ad..8f327cbe55a6dba6b7ae5f104a89f4da46ddf711 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", + }, }, -} +}; diff --git a/pyproject.toml b/pyproject.toml index b5349b77fb39aa4332e18660b86a63de540ade9e..eb1c126332cb282b1523921e001ee7c713851d46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ django-any-js = "^1.1" django-menu-generator-ng = "^1.2.3" django-tables2 = "^2.1" django-phonenumber-field = {version = "^6.1", extras = ["phonenumbers"]} -django-sass-processor = "1.2.1" +django-sass-processor = "1.2.2" libsass = "^0.21.0" colour = "^0.1.5" dynaconf = {version = "^3.1", extras = ["yaml", "toml", "ini"]} diff --git a/tox.ini b/tox.ini index 9b265e53f8a154838b5af4fd2a4d3475e9c69d40..ff7a8c4aa3f0d97c2224ac9e4998fdaa246265c7 100644 --- a/tox.ini +++ b/tox.ini @@ -6,6 +6,7 @@ envlist = py37,py38,py39 [testenv] whitelist_externals = poetry sudo + node skip_install = true envdir = {toxworkdir}/globalenv commands_pre = @@ -14,6 +15,8 @@ commands_pre = poetry run aleksis-admin collectstatic --no-input commands = poetry run pytest --cov=. {posargs} aleksis/ +setenv= + NODE_PATH=cache/node_modules/ [testenv:selenium] setenv = @@ -27,6 +30,8 @@ commands = poetry run black --check --diff aleksis/ poetry run isort -c --diff --stdout aleksis/ poetry run flake8 {posargs} aleksis/ + node cache/node_modules/.bin/prettier --check . + node cache/node_modules/.bin/eslint aleksis/**/*/assets/**/*.{js,vue} [testenv:security] commands = @@ -46,6 +51,7 @@ commands = poetry run make -C docs/ html {posargs} commands = poetry run isort aleksis/ poetry run black aleksis/ + node cache/node_modules/.bin/prettier --write . [testenv:makemessages] commands =