diff --git a/aleksis/core/assets/App.vue b/aleksis/core/assets/App.vue index 569d0ee52cca41015e1882e08d5a744ed6727e08..10a8b1714aa5498e40cd46fd5314a47520907ecb 100644 --- a/aleksis/core/assets/App.vue +++ b/aleksis/core/assets/App.vue @@ -218,7 +218,9 @@ <message-box type="error" v-else-if=" - permissionResults && !checkPermission($route.meta.permission) + whoAmI && + !$apollo.queries.whoAmI.loading && + !checkPermission($route.meta.permission) " > {{ $t("base.no_permission") }} @@ -341,13 +343,10 @@ import gqlWhoAmI from "./whoAmI.graphql"; import gqlMessages from "./messages.graphql"; import gqlSystemProperties from "./systemProperties.graphql"; import gqlCustomMenu from "./customMenu.graphql"; -import gqlGlobalPermissions from "./globalPermissions.graphql"; import gqlSnackbarItems from "./snackbarItems.graphql"; import useRegisterSWMixin from "./mixins/useRegisterSW"; -// import { VOffline } from "@/v-offline"; - export default { data() { return { @@ -356,7 +355,6 @@ export default { systemProperties: null, messages: null, footerMenu: null, - permissionResults: null, permissionNames: [], sideNavMenu: null, accountMenu: null, @@ -377,7 +375,8 @@ export default { } } - this.$data.permissionNames = permArray; + this.permissionNames = permArray; + this.$apollo.queries.whoAmI.refetch(); }, buildMenu(routes, menuKey) { let menu = {}; @@ -391,6 +390,9 @@ export default { !route.parent && (route.meta.permission ? this.checkPermission(route.meta.permission) + : true) && + (route.meta.validators + ? this.checkValidators(route.meta.validators) : true) ) { let menuItem = { @@ -414,6 +416,9 @@ export default { route.parent.name in menu && (route.meta.permission ? this.checkPermission(route.meta.permission) + : true) && + (route.meta.validators + ? this.checkValidators(route.meta.validators) : true) ) { let menuItem = { @@ -430,9 +435,30 @@ export default { }, checkPermission(permissionName) { return ( - this.permissionResults && - this.permissionResults.find((p) => p.name === permissionName) && - this.permissionResults.find((p) => p.name === permissionName).result + this.whoAmI && + this.whoAmI.permissions && + this.whoAmI.permissions.find((p) => p.name === permissionName) && + this.whoAmI.permissions.find((p) => p.name === permissionName).result + ); + }, + checkValidators(validators) { + for (const validator of validators) { + if (!validator(this.whoAmI)) { + return false; + } + } + return true; + }, + buildMenus() { + this.accountMenu = this.buildMenu( + this.$router.getRoutes(), + "inAccountMenu", + this.whoAmI.permissions + ); + this.sideNavMenu = this.buildMenu( + this.$router.getRoutes(), + "inMenu", + this.whoAmI.permissions ); }, }, @@ -440,6 +466,11 @@ export default { systemProperties: gqlSystemProperties, whoAmI: { query: gqlWhoAmI, + variables() { + return { + permissions: this.permissionNames, + }; + }, pollInterval: 10000, }, snackbarItems: { @@ -459,15 +490,6 @@ export default { }, update: (data) => data.customMenuByName, }, - permissionResults: { - query: gqlGlobalPermissions, - variables() { - return { - permissions: this.$data.permissionNames, - }; - }, - update: (data) => data.globalPermissionsByName, - }, }, watch: { systemProperties: function (newProperties) { @@ -482,23 +504,12 @@ export default { this.$vuetify.theme.themes.dark.secondary = newProperties.sitePreferences.themeSecondary; }, - whoAmI: function (user) { - this.$vuetify.theme.dark = - user.person && user.person.preferences.themeDesignMode === "dark"; - this.$apollo.queries.permissionResults.refetch(); - }, - permissionResults: { - handler(newResults) { - this.$data.accountMenu = this.buildMenu( - this.$router.getRoutes(), - "inAccountMenu", - newResults - ); - this.$data.sideNavMenu = this.buildMenu( - this.$router.getRoutes(), - "inMenu", - newResults - ); + whoAmI: { + handler() { + this.$vuetify.theme.dark = + this.whoAmI.person && + this.whoAmI.person.preferences.themeDesignMode === "dark"; + this.buildMenus(); }, deep: true, }, @@ -516,7 +527,6 @@ export default { CeleryProgressBottom, Loading, SnackbarItem, - // VOffline, }, mixins: [useRegisterSWMixin], }; diff --git a/aleksis/core/assets/app.js b/aleksis/core/assets/app.js index 01c0b672dcd41a9c539c4f1894e6bbf08cc8c156..3d9a7f06b028dc3d5ade00f2e83a8cfc63a242ca 100644 --- a/aleksis/core/assets/app.js +++ b/aleksis/core/assets/app.js @@ -1,5 +1,6 @@ import Vue from "vue"; import VueRouter from "@/vue-router"; +import AleksisVue from "./plugins/aleksis.js"; import Vuetify from "@/vuetify"; import "@/@mdi/font/css/materialdesignicons.css"; @@ -62,6 +63,7 @@ i18n.registerLocale = function (messages) { Vue.use(VueApollo); Vue.use(VueRouter); +Vue.use(AleksisVue); export const typeDefs = gql` type snackbarItem { @@ -164,6 +166,9 @@ const router = new VueRouter({ routes, }); +router.afterEach((to, from, next) => { + app.$setPageTitle(null, to); +}); if (document.getElementById("sentry_settings") !== null) { const Sentry = import("@sentry/vue"); const { BrowserTracing } = import("@sentry/tracing"); @@ -193,6 +198,7 @@ const app = new Vue({ data: () => ({ showCacheAlert: false, contentLoading: false, + pageBaseTitle: document.title, }), router, i18n, @@ -211,6 +217,13 @@ router.afterEach((to, from) => { }); }); +// eslint-disable-next-line no-unused-vars +router.beforeEach((to, from, next) => { + console.debug("Setting new page title due to route change"); + app.$setPageTitle(null, to); + next(); +}); + window.app = app; window.router = router; window.i18n = i18n; diff --git a/aleksis/core/assets/components/BrandLogo.vue b/aleksis/core/assets/components/BrandLogo.vue index fa7fce53bb190360615599b5ba0604d9535ef721..8d32dd0c22382b1047df9150a8e3d0c247197a4f 100644 --- a/aleksis/core/assets/components/BrandLogo.vue +++ b/aleksis/core/assets/components/BrandLogo.vue @@ -1,7 +1,7 @@ <template> <img :src="sitePreferences.themeLogo.url" - :alt="sitePreferences.generalTitle + ' – Logo'" + :alt="sitePreferences.generalTitle + ' – ' + $t('base.logo')" class="fullsize" /> </template> diff --git a/aleksis/core/assets/components/Error404.vue b/aleksis/core/assets/components/Error404.vue index 79160f5d4a8abb5bc359c47e11907339bfa7eeee..017f53174179ce346ab40956a8c05d2ff1ea17b4 100644 --- a/aleksis/core/assets/components/Error404.vue +++ b/aleksis/core/assets/components/Error404.vue @@ -5,9 +5,9 @@ > <h1 class="text-h2">{{ $t("network_errors.error_404") }}</h1> <div>{{ $t("network_errors.page_not_found") }}</div> - <v-btn color="secondary" :to="{ name: 'dashboard' }">{{ - $t("network_errors.take_me_back") - }}</v-btn> + <v-btn color="secondary" :to="{ name: 'dashboard' }"> + {{ $t("network_errors.take_me_back") }} + </v-btn> </div> </template> diff --git a/aleksis/core/assets/components/LegacyBaseTemplate.vue b/aleksis/core/assets/components/LegacyBaseTemplate.vue index be51d148b37aa138a8d709811f384a5cd5e44e8e..a295a24ffe104a5368f7e1a1bdae9f3108010134 100644 --- a/aleksis/core/assets/components/LegacyBaseTemplate.vue +++ b/aleksis/core/assets/components/LegacyBaseTemplate.vue @@ -53,7 +53,7 @@ export default { this.$root.contentLoading = true; }; const title = this.$refs.contentIFrame.contentWindow.document.title; - document.title = title; + this.$root.$setPageTitle(title); }, }, watch: { diff --git a/aleksis/core/assets/components/about/AboutAleksis.vue b/aleksis/core/assets/components/about/AboutAleksis.vue index 2f39581d430a06b57bec0a9367983023ddbb9f95..74ba7a0152dba8059039c3681868fa3e69dde239 100644 --- a/aleksis/core/assets/components/about/AboutAleksis.vue +++ b/aleksis/core/assets/components/about/AboutAleksis.vue @@ -13,12 +13,12 @@ </v-card-text> <v-spacer></v-spacer> <v-card-actions> - <v-btn text color="primary" href="https://aleksis.org/">{{ - $t("about.website_of_aleksis") - }}</v-btn> - <v-btn text color="primary" href="https://edugit.org/AlekSIS/">{{ - $t("about.source_code") - }}</v-btn> + <v-btn text color="primary" href="https://aleksis.org/"> + {{ $t("about.website_of_aleksis") }} + </v-btn> + <v-btn text color="primary" href="https://edugit.org/AlekSIS/"> + {{ $t("about.source_code") }} + </v-btn> </v-card-actions> </v-card> </v-col> @@ -30,19 +30,19 @@ {{ $t("about.licence_information_1") }} </p> <p> - <v-chip color="green" text-color="white" small>{{ - $t("about.free_open_source_licence") - }}</v-chip> - <v-chip color="orange" text-color="white" small>{{ - $t("about.other_licence") - }}</v-chip> + <v-chip color="green" text-color="white" small> + {{ $t("about.free_open_source_licence") }} + </v-chip> + <v-chip color="orange" text-color="white" small> + {{ $t("about.other_licence") }} + </v-chip> </p> </v-card-text> <v-spacer></v-spacer> <v-card-actions> - <v-btn text color="primary" href="https://eupl.eu">{{ - $t("about.full_licence_text") - }}</v-btn> + <v-btn text color="primary" href="https://eupl.eu"> + {{ $t("about.full_licence_text") }} + </v-btn> <v-btn text color="primary" diff --git a/aleksis/core/assets/components/about/InstalledAppCard.vue b/aleksis/core/assets/components/about/InstalledAppCard.vue index 796808abd127324caf48d71e5e79afcc24da8380..99f63dfd0af39e33a8da7f4e3289883baa16a580 100644 --- a/aleksis/core/assets/components/about/InstalledAppCard.vue +++ b/aleksis/core/assets/components/about/InstalledAppCard.vue @@ -13,9 +13,9 @@ <v-row v-if="app.licence" class="mb-2"> <v-col cols="6"> {{ $t("about.licenced_under") }} <br /> - <strong class="text-body-1 black--text">{{ - app.licence.verboseName - }}</strong> + <strong class="text-body-1 black--text"> + {{ app.licence.verboseName }} + </strong> </v-col> <v-col cols="6"> {{ $t("about.licence_type") }} <br /> @@ -75,7 +75,7 @@ <v-card-actions v-if="app.urls.length !== 0"> <v-btn text color="primary" @click="reveal = true"> - Show copyright + {{ $t("about.show_copyright") }} </v-btn> <v-btn v-for="url in app.urls" @@ -98,9 +98,9 @@ <v-col cols="12" v-if="app.copyrights.length !== 0"> <span v-for="(copyright, index) in app.copyrights" :key="index"> Copyright © {{ copyright.years }} - <a :href="'mailto:' + copyright.email">{{ - copyright.name - }}</a> + <a :href="'mailto:' + copyright.email"> + {{ copyright.name }} + </a> <br /> </span> </v-col> @@ -108,7 +108,9 @@ </v-card-text> <v-spacer></v-spacer> <v-card-actions class="pt-0"> - <v-btn text color="primary" @click="reveal = false"> Close </v-btn> + <v-btn text color="primary" @click="reveal = false">{{ + $t("actions.close") + }}</v-btn> </v-card-actions> </v-card> </v-expand-transition> diff --git a/aleksis/core/assets/components/generic/ListView.vue b/aleksis/core/assets/components/generic/ListView.vue index 7a78a7643b2f08846f341677eb82074eda31013e..bc68b2dbf9f5a5dea29a792cab497ffe74931b3e 100644 --- a/aleksis/core/assets/components/generic/ListView.vue +++ b/aleksis/core/assets/components/generic/ListView.vue @@ -20,9 +20,7 @@ export default { components: { DetailView, }, -} +}; </script> -<style scoped> - -</style> +<style scoped></style> diff --git a/aleksis/core/assets/components/person/AdditionalImage.vue b/aleksis/core/assets/components/person/AdditionalImage.vue index 5ea3479bb42ebcac6211874c022454c7677b8968..f483705806bb5eb718ccf136a4773b4a5bac7130 100644 --- a/aleksis/core/assets/components/person/AdditionalImage.vue +++ b/aleksis/core/assets/components/person/AdditionalImage.vue @@ -27,7 +27,7 @@ <v-list> <v-list-item> <v-list-item-icon> - <v-icon> mdi-image-off-outline </v-icon> + <v-icon>mdi-image-off-outline</v-icon> </v-list-item-icon> <v-list-item-content> diff --git a/aleksis/core/assets/components/person/PersonActions.vue b/aleksis/core/assets/components/person/PersonActions.vue index 2b0bc7bc6e55d985b6c66c3d7b9dbc92b13344b4..9e9518c9a67e6bc95ee2bd09258e6f80798c76f5 100644 --- a/aleksis/core/assets/components/person/PersonActions.vue +++ b/aleksis/core/assets/components/person/PersonActions.vue @@ -1,36 +1,36 @@ <template> <ApolloQuery :query="require('./personActions.graphql')" :variables="{ id }"> <template #default="{ result: { error, data, loading } }"> - <v-skeleton-loader v-if="loading" type="actions"/> + <v-skeleton-loader v-if="loading" type="actions" /> <template v-else-if="data && data.person && data.person.id"> <v-btn - v-if="data.person.canEditPerson" - color="primary" - :to="{ name: 'core.editPerson', params: { id: data.person.id } }" + v-if="data.person.canEditPerson" + color="primary" + :to="{ name: 'core.editPerson', params: { id: data.person.id } }" > <v-icon left>$edit</v-icon> {{ $t("actions.edit") }} </v-btn> <v-btn - v-if="data.person.canChangePersonPreferences" - color="secondary" - outlined - text - :to="{ - name: 'core.preferencesPersonByPk', - params: { pk: data.person.id }, - }" + v-if="data.person.canChangePersonPreferences" + color="secondary" + outlined + text + :to="{ + name: 'core.preferencesPersonByPk', + params: { pk: data.person.id }, + }" > <v-icon left>$preferences</v-icon> {{ $t("preferences.person.change_preferences") }} </v-btn> <v-menu - v-if=" - data.person.canImpersonatePerson || - data.person.canInvitePerson || - data.person.canDeletePerson - " + v-if=" + data.person.canImpersonatePerson || + data.person.canInvitePerson || + data.person.canDeletePerson + " > <template #activator="{ on, attrs }"> <v-btn outlined text v-bind="attrs" v-on="on"> @@ -39,57 +39,54 @@ </template> <v-list> <v-list-item - v-if="data.person.canImpersonatePerson" - :to="{ - name: 'impersonate.impersonateByUserPk', - params: { uid: data.person.userid }, - query: { next: $route.path }, - }" + v-if="data.person.canImpersonatePerson" + :to="{ + name: 'impersonate.impersonateByUserPk', + params: { uid: data.person.userid }, + query: { next: $route.path }, + }" > <v-list-item-icon> <v-icon>mdi-account-box-outline</v-icon> </v-list-item-icon> <v-list-item-content> - <v-list-item-title>{{ - $t("person.impersonation.impersonate") - }} + <v-list-item-title> + {{ $t("person.impersonation.impersonate") }} </v-list-item-title> </v-list-item-content> </v-list-item> <v-list-item - v-if="data.person.canInvitePerson" - :to="{ - name: 'core.invitePerson', - params: { id: data.person.id }, - }" + v-if="data.person.canInvitePerson" + :to="{ + name: 'core.invitePerson', + params: { id: data.person.id }, + }" > <v-list-item-icon> <v-icon>mdi-account-plus-outline</v-icon> </v-list-item-icon> <v-list-item-content> - <v-list-item-title>{{ - $t("person.invite") - }} + <v-list-item-title> + {{ $t("person.invite") }} </v-list-item-title> </v-list-item-content> </v-list-item> <v-list-item - v-if="data.person.canDeletePerson" - :to="{ - name: 'core.deletePerson', - params: { id: data.person.id }, - }" - class="error--text" + v-if="data.person.canDeletePerson" + :to="{ + name: 'core.deletePerson', + params: { id: data.person.id }, + }" + class="error--text" > <v-list-item-icon> <v-icon color="error">mdi-delete</v-icon> </v-list-item-icon> <v-list-item-content> - <v-list-item-title>{{ - $t("person.delete") - }} + <v-list-item-title> + {{ $t("person.delete") }} </v-list-item-title> </v-list-item-content> </v-list-item> @@ -112,6 +109,4 @@ export default { }; </script> -<style scoped> - -</style> +<style scoped></style> diff --git a/aleksis/core/assets/components/person/PersonOverview.vue b/aleksis/core/assets/components/person/PersonOverview.vue index 4aabae0faa0914b800634e714c1f8e69c807daf7..be36914c7b316e0ef415ec192eadfb82207ed3a7 100644 --- a/aleksis/core/assets/components/person/PersonOverview.vue +++ b/aleksis/core/assets/components/person/PersonOverview.vue @@ -82,38 +82,54 @@ </v-list-item> <v-divider inset /> - <v-list-item :href="'tel:' + data.person.phoneNumber"> + <v-list-item + :href=" + data.person.phoneNumber + ? 'tel:' + data.person.phoneNumber + : '' + " + > <v-list-item-icon> <v-icon> mdi-phone-outline</v-icon> </v-list-item-icon> <v-list-item-content> - <v-list-item-title - >{{ data.person.phoneNumber || "–" }} + <v-list-item-title> + {{ data.person.phoneNumber || "–" }} </v-list-item-title> - <v-list-item-subtitle - >{{ $t("person.home") }} + <v-list-item-subtitle> + {{ $t("person.home") }} </v-list-item-subtitle> </v-list-item-content> </v-list-item> - <v-list-item :href="'tel:' + data.person.mobileNumber"> + <v-list-item + :href=" + data.person.mobileNumber + ? 'tel:' + data.person.mobileNumber + : '' + " + > <v-list-item-action></v-list-item-action> <v-list-item-content> - <v-list-item-title - >{{ data.person.mobileNumber || "–" }} + <v-list-item-title> + {{ data.person.mobileNumber || "–" }} </v-list-item-title> - <v-list-item-subtitle - >{{ $t("person.mobile") }} + <v-list-item-subtitle> + {{ $t("person.mobile") }} </v-list-item-subtitle> </v-list-item-content> </v-list-item> <v-divider inset /> - <v-list-item :href="'mailto:' + data.person.email"> + <v-list-item + :href=" + data.person.email ? 'mailto:' + data.person.email : '' + " + > <v-list-item-icon> - <v-icon> mdi-email-outline</v-icon> + <v-icon>mdi-email-outline</v-icon> </v-list-item-icon> <v-list-item-content> diff --git a/aleksis/core/assets/globalPermissions.graphql b/aleksis/core/assets/globalPermissions.graphql deleted file mode 100644 index 0d521e45e26ac6f22bba64d08a1f04b57e59dff2..0000000000000000000000000000000000000000 --- a/aleksis/core/assets/globalPermissions.graphql +++ /dev/null @@ -1,6 +0,0 @@ -query ($permissions: [String]!) { - globalPermissionsByName(permissions: $permissions) { - name - result - } -} diff --git a/aleksis/core/assets/messages/en.json b/aleksis/core/assets/messages/en.json index b2679b64a49563ff4a9491cfe891ef4d6ecfe6f5..d4a5e52784afd429cced438b0380faf5a421104a 100644 --- a/aleksis/core/assets/messages/en.json +++ b/aleksis/core/assets/messages/en.json @@ -16,7 +16,8 @@ "other_licence": "Other Licence", "proprietary": "Proprietary", "source_code": "Source Code", - "website_of_aleksis": "Website of AlekSIS" + "website_of_aleksis": "Website of AlekSIS", + "show_copyright": "Show copyright" }, "accounts": { "change_password": { @@ -49,7 +50,8 @@ "actions": { "back": "Back", "search": "Search", - "edit": "Edit" + "edit": "Edit", + "close": "Close" }, "administration": { "backend_admin": { @@ -74,7 +76,8 @@ "person_is_dummy": "Your administrator account is not linked to any person. Therefore, a dummy person has been linked to your account.", "privacy_policy": "Privacy Policy", "user_not_linked_to_person": "Your user account is not linked to a person. This means you cannot access any school-related information. Please contact the managers of AlekSIS at your school.", - "no_permission": "You have no permission to view this page. Please login with an other account." + "no_permission": "You have no permission to view this page. Please login with an other account.", + "logo": "Logo" }, "celery_progress": { "error_message": "The operation couldn't be finished successfully.", diff --git a/aleksis/core/assets/plugins/aleksis.js b/aleksis/core/assets/plugins/aleksis.js new file mode 100644 index 0000000000000000000000000000000000000000..87ebfcb680576db41acad426c6c1b3055e213fc8 --- /dev/null +++ b/aleksis/core/assets/plugins/aleksis.js @@ -0,0 +1,28 @@ +const AleksisVue = {}; + +console.debug("Defining AleksisVue plugin"); + +AleksisVue.install = function (Vue, options) { + Vue.prototype.$setPageTitle = function (title, route) { + let titleParts = []; + + + if (title) { + titleParts.push(title); + } else { + if (!route) { + route = this.$route; + }; + if (route.meta.titleKey) { + titleParts.push(this.$t(route.meta.titleKey)); + } + } + + titleParts.push(this.pageBaseTitle); + const newTitle = titleParts.join(" – "); + console.debug(`Setting page title: ${newTitle}`); + document.title = newTitle; + }; +}; + +export default AleksisVue; diff --git a/aleksis/core/assets/routeValidators.js b/aleksis/core/assets/routeValidators.js new file mode 100644 index 0000000000000000000000000000000000000000..5226209a5419f87f79308e174bf329c3a32ca642 --- /dev/null +++ b/aleksis/core/assets/routeValidators.js @@ -0,0 +1,5 @@ +const notLoggedInValidator = (whoAmI) => { + return !whoAmI; +}; + +export { notLoggedInValidator }; diff --git a/aleksis/core/assets/routes.js b/aleksis/core/assets/routes.js index 24dcb93d146afaedd1b27a74f4750339b1ac7d93..a69cb07c4ebb1b83c99f045cc43b7deeeb850fd9 100644 --- a/aleksis/core/assets/routes.js +++ b/aleksis/core/assets/routes.js @@ -4,6 +4,8 @@ // and generates importing code at bundle time. import apps from "aleksisAppImporter"; +import { notLoggedInValidator } from "./routeValidators"; + const routes = [ { path: "/account/login/", @@ -14,6 +16,7 @@ const routes = [ icon: "mdi-login-variant", titleKey: "accounts.login.menu_title", permission: "core.login_rule", + validators: [notLoggedInValidator], }, }, { @@ -25,6 +28,7 @@ const routes = [ icon: "mdi-account-plus-outline", titleKey: "accounts.signup.menu_title", permission: "core.signup_rule", + validators: [notLoggedInValidator], }, }, { @@ -36,6 +40,7 @@ const routes = [ icon: "mdi-key-outline", titleKey: "accounts.invitation.accept_invitation.menu_title", permission: "core.accept_invite_rule", + validators: [notLoggedInValidator], }, }, { @@ -81,6 +86,9 @@ const routes = [ component: () => import("./components/person/PersonOverview.vue"), name: "core.personById", props: true, + meta: { + titleKey: "person.pageTitle", + }, }, { path: "/persons/:id(\\d+)/edit/", @@ -752,6 +760,9 @@ const routes = [ path: "/about", component: () => import("./components/about/About.vue"), name: "core.about", + meta: { + titleKey: "about.pageTitle", + }, }, ]; diff --git a/aleksis/core/assets/whoAmI.graphql b/aleksis/core/assets/whoAmI.graphql index 0137faa7b414bf338dea39dcfcc220c60af8326c..7223ea0c8c618f091bb4036764d962abf335d62c 100644 --- a/aleksis/core/assets/whoAmI.graphql +++ b/aleksis/core/assets/whoAmI.graphql @@ -1,4 +1,4 @@ -{ +query ($permissions: [String]!) { whoAmI { username isAuthenticated @@ -15,5 +15,9 @@ themeDesignMode } } + permissions: globalPermissionsByName(permissions: $permissions) { + name + result + } } } diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py index d043bed2296da612408b4c99b5e21e08f8ccec3e..ba4021e9475e534a4acf208a965618ccfd310f5f 100644 --- a/aleksis/core/rules.py +++ b/aleksis/core/rules.py @@ -353,10 +353,10 @@ rules.add_perm("core.reset_password_rule", reset_password_predicate) invite_enabled_predicate = is_site_preference_set(section="auth", pref="invite_enabled") rules.add_perm("core.invite_enabled", invite_enabled_predicate) -accept_invite_predicate = invite_enabled_predicate +accept_invite_predicate = has_person & invite_enabled_predicate rules.add_perm("core.accept_invite_rule", accept_invite_predicate) -invite_predicate = invite_enabled_predicate & has_person & has_global_perm("core.invite") +invite_predicate = has_person & invite_enabled_predicate & has_global_perm("core.invite") rules.add_perm("core.invite_rule", invite_predicate) # OAuth2 permissions diff --git a/aleksis/core/schema/__init__.py b/aleksis/core/schema/__init__.py index 66dab37884196b9048dc81178d2bfa3ce8ebdd13..a4ed8158083184e0451e1b8d03dab25bc581d0ef 100644 --- a/aleksis/core/schema/__init__.py +++ b/aleksis/core/schema/__init__.py @@ -19,7 +19,6 @@ from .installed_apps import AppType from .message import MessageType from .notification import MarkNotificationReadMutation, NotificationType from .pdf import PDFFileType -from .permissions import GlobalPermissionType from .person import PersonMutation, PersonType from .school_term import SchoolTermType # noqa from .search import SearchResultType @@ -54,10 +53,6 @@ class Query(graphene.ObjectType): custom_menu_by_name = graphene.Field(CustomMenuType, name=graphene.String()) - global_permissions_by_name = graphene.List( - GlobalPermissionType, permissions=graphene.List(graphene.String) - ) - def resolve_notifications(root, info, **kwargs): return Notification.objects.filter( Q( @@ -136,12 +131,6 @@ class Query(graphene.ObjectType): def resolve_custom_menu_by_name(root, info, name, **kwargs): return CustomMenu.get_default(name) - def resolve_global_permissions_by_name(root, info, permissions, **kwargs): - return [ - {"name": permission_name, "result": info.context.user.has_perm(permission_name)} - for permission_name in permissions - ] - class Mutation(graphene.ObjectType): update_person = PersonMutation.Field() diff --git a/aleksis/core/schema/user.py b/aleksis/core/schema/user.py index 33547acf8a0f0ea5141916d15f2636783da594ec..1c1ff08ea2c4c77a6ab68af9e389a36db66a764c 100644 --- a/aleksis/core/schema/user.py +++ b/aleksis/core/schema/user.py @@ -1,5 +1,6 @@ import graphene +from .permissions import GlobalPermissionType from .person import PersonType @@ -13,3 +14,13 @@ class UserType(graphene.ObjectType): is_anonymous = graphene.Boolean(required=True) person = graphene.Field(PersonType) + + global_permissions_by_name = graphene.List( + GlobalPermissionType, permissions=graphene.List(graphene.String) + ) + + def resolve_global_permissions_by_name(root, info, permissions, **kwargs): + return [ + {"name": permission_name, "result": info.context.user.has_perm(permission_name)} + for permission_name in permissions + ] diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html index 6c35b2b943881e1f433830fc723f5380254a4d67..8adfdd6a0f9cdcbbd25dc088969ccd1a5fe252e1 100644 --- a/aleksis/core/templates/core/base.html +++ b/aleksis/core/templates/core/base.html @@ -51,9 +51,8 @@ <title> {% block no_browser_title %} - {% block browser_title %}{% endblock %} — + {% block browser_title %}{% endblock %} {% endblock %} - {{ request.site.preferences.general__title }} </title> @@ -83,6 +82,33 @@ window.parent.postMessage({height: $(document).height()}); }; window.onresize = documentResizePostMessage; + + function findLink(el) { + if (el.href) { + return el.href; + } else if (el.parentElement) { + return findLink(el.parentElement); + } else { + return null; + } + }; + + function clickCallback(e) { + const link = findLink(e.target); + if (link == null) { + return; + } + + const newUrl = new URL(link); + const currentUrl = new URL(window.location.href); + if(newUrl.origin !== currentUrl.origin) { + console.debug("External link clicked. Redirecting to " + link + " in new tab."); + e.preventDefault(); + window.open(link, '_blank'); + } + }; + + document.addEventListener('click', clickCallback, false); }) </script> </body> diff --git a/aleksis/core/util/frontend_helpers.py b/aleksis/core/util/frontend_helpers.py index 7805829f06b522c77d767990e83d549195429e7d..606ca372cea1c14641632125dcee346e73e00daf 100644 --- a/aleksis/core/util/frontend_helpers.py +++ b/aleksis/core/util/frontend_helpers.py @@ -34,7 +34,7 @@ def write_vite_values(out_path: str) -> dict[str, Any]: vite_values["appDetails"][app]["name"] = app.split(".")[-1] vite_values["appDetails"][app]["assetDir"] = path vite_values["appDetails"][app]["hasMessages"] = os.path.exists( - os.path.join(path, "messages.json") + os.path.join(path, "messages", "en.json") ) # Add core entrypoint vite_values["coreAssetDir"] = os.path.join(settings.BASE_DIR, "aleksis", "core", "assets") diff --git a/aleksis/core/vite.config.js b/aleksis/core/vite.config.js index f33c3d631d71e7909994f35147853c0ee3ca1822..fa51a4854937f3896e50ea308037009a8f391e77 100644 --- a/aleksis/core/vite.config.js +++ b/aleksis/core/vite.config.js @@ -98,7 +98,7 @@ export default defineConfig({ strictPort: true, origin: "http://localhost:5173", watch: { - ignored: ["**/*.py", "**/__pycache__/**"], + ignored: ["**/*.py", "**/__pycache__/**", "**/*.mo", "**/.venv/**", "**/.tox/**"], }, fs: { allow: [