Merge branch 'development' into 'main'

Development

See merge request htwk-software/htwkalender!49
This commit is contained in:
Elmar Kresse
2024-07-03 21:27:18 +00:00
37 changed files with 1009 additions and 256 deletions

1
frontend/.gitignore vendored
View File

@ -10,6 +10,7 @@ lerna-debug.log*
node_modules node_modules
dist dist
dist-ssr dist-ssr
.vite-ssg-temp
*.local *.local
# Editor directories and files # Editor directories and files

View File

@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="de">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" sizes="32x32" /> <link rel="icon" href="/favicon.ico" sizes="32x32" />
@ -12,10 +12,9 @@
href="/themes/lara-light-blue/theme.css" href="/themes/lara-light-blue/theme.css"
/> />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>HTWKalender</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"><!--app-html--></div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

View File

@ -50,7 +50,7 @@ http {
index index.html index.htm; index index.html index.htm;
#necessary to display vue subpage #necessary to display vue subpage
try_files $uri $uri/ /index.html; try_files $uri $uri.html $uri/ /index.html;
} }
error_page 500 502 503 504 /50x.html; error_page 500 502 503 504 /50x.html;
location = /50x.html { location = /50x.html {

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc && vite build", "build": "vue-tsc && vite-ssg build",
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src", "lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
"lint-no-fix": "eslint --ext .js,.vue --ignore-path .gitignore src", "lint-no-fix": "eslint --ext .js,.vue --ignore-path .gitignore src",
@ -20,6 +20,7 @@
"@fullcalendar/vue3": "^6.1.11", "@fullcalendar/vue3": "^6.1.11",
"@tanstack/vue-query": "^5.28.9", "@tanstack/vue-query": "^5.28.9",
"@tanstack/vue-query-devtools": "^5.28.10", "@tanstack/vue-query-devtools": "^5.28.10",
"@unhead/ssr": "^1.9.14",
"@unhead/vue": "^1.9.10", "@unhead/vue": "^1.9.10",
"@vueuse/core": "^10.9.0", "@vueuse/core": "^10.9.0",
"pinia": "^2.1.7", "pinia": "^2.1.7",
@ -27,6 +28,7 @@
"primeicons": "^6.0.1", "primeicons": "^6.0.1",
"primevue": "^3.50.0", "primevue": "^3.50.0",
"source-sans": "^3.46.0", "source-sans": "^3.46.0",
"vite-ssg": "^0.23.7",
"vue": "^3.4.11", "vue": "^3.4.11",
"vue-i18n": "^9.10.2", "vue-i18n": "^9.10.2",
"vue-router": "^4.3.0" "vue-router": "^4.3.0"
@ -46,6 +48,7 @@
"terser": "^5.31.0", "terser": "^5.31.0",
"typescript": "^5.4.3", "typescript": "^5.4.3",
"vite": "^5.2.7", "vite": "^5.2.7",
"vite-ssg-sitemap": "^0.7.1",
"vitest": "^1.4.0", "vitest": "^1.4.0",
"vue-tsc": "^1.8.27" "vue-tsc": "^1.8.27"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -1,2 +0,0 @@
User-agent: *
Allow: /

View File

@ -19,7 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script lang="ts" setup> <script lang="ts" setup>
import MenuBar from "./components/MenuBar.vue"; import MenuBar from "./components/MenuBar.vue";
import { RouteRecordName, RouterView, useRoute, useRouter } from "vue-router"; import { RouteRecordName, RouterView, useRoute, useRouter } from "vue-router";
import { useHead } from "@unhead/vue"; import { useHead, useServerHead, useServerSeoMeta } from "@unhead/vue";
import CalendarPreview from "./components/CalendarPreview.vue"; import CalendarPreview from "./components/CalendarPreview.vue";
import moduleStore from "./store/moduleStore.ts"; import moduleStore from "./store/moduleStore.ts";
import { computed, provide, ref } from "vue"; import { computed, provide, ref } from "vue";
@ -42,12 +42,19 @@ const disabledPages = [
// Provide canonical link for SEO // Provide canonical link for SEO
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
const baseUri = "https://cal.htwk-leipzig.de"; // could be stored in .env
const domain = "cal.htwk-leipzig.de"
provide('domain', domain);
const baseUri = "https://" + domain;
const canonical = computed(() => `${baseUri}${router.resolve(route.name ? { name: route.name } : route).path}`); const canonical = computed(() => `${baseUri}${router.resolve(route.name ? { name: route.name } : route).path}`);
const title = computed(() => route.meta.label? const title = computed(() => route.meta.label?
`HTWKalender - ${t(String(route.meta.label))}`: `HTWKalender - ${t(String(route.meta.label))}`:
"HTWKalender" "HTWKalender"
); );
const description = computed(() => route.meta.description?
t(String(route.meta.description)):
t("description")
);
useHead({ useHead({
title: title, title: title,
@ -55,17 +62,33 @@ useHead({
{ rel: "canonical", href: canonical}, { rel: "canonical", href: canonical},
], ],
meta: [ meta: [
{ { name: "description", content: description},
name: "description", { property: "og:description", content: description},
content: "Dein individueller Stundenplan mit Sportevents und Prüfungen. Finde kommende Veranstaltungen oder freie Räume zum Lernen und Arbeiten.",
},
{
name: "keywords",
content: "HTWK Leipzig, Stundenplan, iCal, freie Räume, Lerngruppen, Sport, Prüfungen",
}
] ]
}); });
// SEO optimization
useServerHead({
title: title
});
useServerSeoMeta(
{
title: title,
description: description,
keywords: "HTWK Leipzig, Stundenplan, iCal, freie Räume, Lerngruppen, Sport, Prüfungen",
// openGraph
ogTitle: title,
ogDescription: description,
ogImage: `${baseUri}/img/banner-image.png`,
ogImageType: "image/png",
ogLocale: "de_DE",
ogUrl: canonical,
// twitter
twitterCard: "summary_large_image",
twitterSite: "@HTWKLeipzig",
}
);
const store = moduleStore(); const store = moduleStore();
const mobilePage = ref(true); const mobilePage = ref(true);
provide("mobilePage", mobilePage); provide("mobilePage", mobilePage);
@ -75,12 +98,14 @@ const isDisabled = (routeName: RouteRecordName | null | undefined) => {
}; };
const updateMobile = () => { const updateMobile = () => {
if (import.meta.env.SSR) return;
mobilePage.value = window.innerWidth <= 992; mobilePage.value = window.innerWidth <= 992;
}; };
updateMobile(); updateMobile();
window.addEventListener("resize", updateMobile); if (!import.meta.env.SSR)
window.addEventListener("resize", updateMobile);
</script> </script>
<template> <template>

View File

@ -17,6 +17,9 @@
import { Module } from "../model/module.ts"; import { Module } from "../model/module.ts";
export async function createIndividualFeed(modules: Module[]): Promise<string> { export async function createIndividualFeed(modules: Module[]): Promise<string> {
if (import.meta.env.SSR) {
return "";
}
try { try {
const response = await fetch("/api/feed", { const response = await fetch("/api/feed", {
method: "POST", method: "POST",
@ -62,6 +65,9 @@ export async function saveIndividualFeed(
token: string, token: string,
modules: Module[], modules: Module[],
): Promise<string> { ): Promise<string> {
if (import.meta.env.SSR) {
return "";
}
await fetch("/api/collections/feeds/records/" + token, { await fetch("/api/collections/feeds/records/" + token, {
method: "PATCH", method: "PATCH",
headers: { headers: {
@ -81,6 +87,9 @@ export async function saveIndividualFeed(
} }
export async function deleteIndividualFeed(token: string): Promise<void> { export async function deleteIndividualFeed(token: string): Promise<void> {
if (import.meta.env.SSR) {
return;
}
await fetch("/api/feed?token=" + token, { await fetch("/api/feed?token=" + token, {
method: "DELETE", method: "DELETE",
}) })

View File

@ -19,6 +19,9 @@
import { Module } from "../model/module.ts"; import { Module } from "../model/module.ts";
export async function fetchCourse(): Promise<string[]> { export async function fetchCourse(): Promise<string[]> {
if (import.meta.env.SSR) {
return [];
}
const courses: string[] = []; const courses: string[] = [];
await fetch("/api/courses") await fetch("/api/courses")
.then((response) => { .then((response) => {
@ -39,6 +42,9 @@ export async function fetchCourse(): Promise<string[]> {
export async function fetchCourseBySemester( export async function fetchCourseBySemester(
semester: string, semester: string,
): Promise<string[]> { ): Promise<string[]> {
if (import.meta.env.SSR) {
return [];
}
const courses: string[] = []; const courses: string[] = [];
await fetch("/api/courses/events?semester=" + semester) await fetch("/api/courses/events?semester=" + semester)
.then((response) => { .then((response) => {
@ -60,6 +66,9 @@ export async function fetchModulesByCourseAndSemester(
course: string, course: string,
semester: string, semester: string,
): Promise<Module[]> { ): Promise<Module[]> {
if (import.meta.env.SSR) {
return [];
}
const modules: Module[] = []; const modules: Module[] = [];
await fetch("/api/course/modules?course=" + course + "&semester=" + semester) await fetch("/api/course/modules?course=" + course + "&semester=" + semester)
.then((response) => { .then((response) => {
@ -86,6 +95,9 @@ export async function fetchModulesByCourseAndSemester(
} }
export async function fetchAllModules(): Promise<Module[]> { export async function fetchAllModules(): Promise<Module[]> {
if (import.meta.env.SSR) {
return [];
}
const modules: Module[] = []; const modules: Module[] = [];
await fetch("/api/modules") await fetch("/api/modules")
.then((response) => { .then((response) => {

View File

@ -17,6 +17,9 @@
// function to fetch course data from the API // function to fetch course data from the API
export async function fetchEventTypes(): Promise<string[]> { export async function fetchEventTypes(): Promise<string[]> {
if (import.meta.env.SSR) {
return [];
}
const eventTypes: string[] = []; const eventTypes: string[] = [];
await fetch("/api/events/types") await fetch("/api/events/types")
.then((response) => { .then((response) => {
@ -30,4 +33,4 @@ export async function fetchEventTypes(): Promise<string[]> {
}); });
}); });
return eventTypes; return eventTypes;
} }

View File

@ -17,6 +17,9 @@
import { Module } from "../model/module"; import { Module } from "../model/module";
export async function fetchModule(module: Module): Promise<Module> { export async function fetchModule(module: Module): Promise<Module> {
if (import.meta.env.SSR) {
return new Module("", "", "", "", "", "", "", false, []);
}
// request to the data-manager on /api/module with query parameters name as the module name // request to the data-manager on /api/module with query parameters name as the module name
const request = new Request("/api/module?uuid=" + module.uuid); const request = new Request("/api/module?uuid=" + module.uuid);

View File

@ -17,6 +17,9 @@
import { AnonymizedEventDTO } from "../model/event.ts"; import { AnonymizedEventDTO } from "../model/event.ts";
export async function fetchRoom(): Promise<string[]> { export async function fetchRoom(): Promise<string[]> {
if (import.meta.env.SSR) {
return [];
}
const rooms: string[] = []; const rooms: string[] = [];
await fetch("/api/rooms") await fetch("/api/rooms")
.then((response) => { .then((response) => {
@ -33,6 +36,9 @@ export async function fetchEventsByRoomAndDuration(
from_date: string, from_date: string,
to_date: string, to_date: string,
): Promise<AnonymizedEventDTO[]> { ): Promise<AnonymizedEventDTO[]> {
if (import.meta.env.SSR) {
return [];
}
const events: AnonymizedEventDTO[] = []; const events: AnonymizedEventDTO[] = [];
await fetch( await fetch(
"/api/schedule?room=" + room + "&from=" + from_date + "&to=" + to_date, "/api/schedule?room=" + room + "&from=" + from_date + "&to=" + to_date,

View File

@ -18,6 +18,9 @@ import { Module } from "../model/module";
import { Calendar } from "../model/calendar"; import { Calendar } from "../model/calendar";
export async function getCalender(token: string): Promise<Module[]> { export async function getCalender(token: string): Promise<Module[]> {
if (import.meta.env.SSR) {
return [];
}
const request = new Request("/api/collections/feeds/records/" + token, { const request = new Request("/api/collections/feeds/records/" + token, {
method: "GET", method: "GET",
}); });

View File

@ -19,6 +19,9 @@ export async function requestFreeRooms(
from: string, from: string,
to: string, to: string,
): Promise<string[]> { ): Promise<string[]> {
if (import.meta.env.SSR) {
return [];
}
const rooms: string[] = []; const rooms: string[] = [];
await fetch("/api/rooms/free?from=" + from + "&to=" + to) await fetch("/api/rooms/free?from=" + from + "&to=" + to)
.then((response) => { .then((response) => {

View File

@ -27,7 +27,7 @@ import {
DataTableRowUnselectEvent, DataTableRowUnselectEvent,
} from "primevue/datatable"; } from "primevue/datatable";
import { useDialog } from "primevue/usedialog"; import { useDialog } from "primevue/usedialog";
import router from "../router"; import { router } from "@/main";
import { fetchModule } from "../api/fetchModule.ts"; import { fetchModule } from "../api/fetchModule.ts";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { fetchEventTypes } from "../api/fetchEvents.ts"; import { fetchEventTypes } from "../api/fetchEvents.ts";

View File

@ -24,7 +24,7 @@ const PrimeVue = usePrimeVue();
const emit = defineEmits(["dark-mode-toggled"]); const emit = defineEmits(["dark-mode-toggled"]);
const isDark = ref(true); const isDark = ref(false);
const darkTheme = ref("lara-dark-blue"), const darkTheme = ref("lara-dark-blue"),
lightTheme = ref("lara-light-blue"); lightTheme = ref("lara-light-blue");

View File

@ -26,7 +26,7 @@ import { CalendarOptions, DatesSetArg, EventInput } from "@fullcalendar/core";
import { fetchEventsByRoomAndDuration } from "../api/fetchRoom.ts"; import { fetchEventsByRoomAndDuration } from "../api/fetchRoom.ts";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import allLocales from "@fullcalendar/core/locales-all"; import allLocales from "@fullcalendar/core/locales-all";
import router from "@/router"; import { router } from "@/main";
import { formatYearMonthDay } from "@/helpers/dates"; import { formatYearMonthDay } from "@/helpers/dates";
import { useQuery } from "@tanstack/vue-query"; import { useQuery } from "@tanstack/vue-query";
import { watch } from "vue"; import { watch } from "vue";

View File

@ -27,7 +27,7 @@ function setup() {
_i18n = createI18n({ _i18n = createI18n({
legacy: false, legacy: false,
locale: localeStore().locale, locale: localeStore().locale,
fallbackLocale: "en", fallbackLocale: "de",
messages: { messages: {
en, en,
de, de,

View File

@ -6,6 +6,7 @@
"faq": "FAQ", "faq": "FAQ",
"imprint": "Impressum", "imprint": "Impressum",
"privacy": "Datenschutz", "privacy": "Datenschutz",
"description": "Dein individueller Stundenplan mit Sportevents und Prüfungen. Finde kommende Veranstaltungen oder freie Räume zum Lernen und Arbeiten.",
"english": "Englisch", "english": "Englisch",
"german": "Deutsch", "german": "Deutsch",
"courseSelection": { "courseSelection": {
@ -22,6 +23,7 @@
"roomSchedule": "Raumbelegung", "roomSchedule": "Raumbelegung",
"headline": "Raumbelegung", "headline": "Raumbelegung",
"detail": "Bitte wähle einen Raum aus, um die Belegung einzusehen", "detail": "Bitte wähle einen Raum aus, um die Belegung einzusehen",
"description": "Möchtest du schnell checken, ob ein Raum frei ist? Hier kannst du die Belegung der Räume an der HTWK Leipzig einsehen.",
"dropDownSelect": "Bitte wähle einen Raum aus", "dropDownSelect": "Bitte wähle einen Raum aus",
"noRoomsAvailable": "Keine Räume verfügbar", "noRoomsAvailable": "Keine Räume verfügbar",
"available": "verfügbar", "available": "verfügbar",
@ -30,6 +32,7 @@
"freeRooms": { "freeRooms": {
"freeRooms": "Freie Räume", "freeRooms": "Freie Räume",
"detail": "Bitte wähle einen Zeitraum aus, um alle Räume ohne Belegung anzuzeigen.", "detail": "Bitte wähle einen Zeitraum aus, um alle Räume ohne Belegung anzuzeigen.",
"description": "Freier Lerngruppenraum gesucht? Hier kannst du alle freien Räume in einem bestimmten Zeitraum an der HTWK Leipzig einsehen.",
"searchByRoom": "Suche nach Räumen", "searchByRoom": "Suche nach Räumen",
"pleaseSelectDate": "Bitte wähle ein Datum aus", "pleaseSelectDate": "Bitte wähle ein Datum aus",
"room": "Raum", "room": "Raum",
@ -68,6 +71,7 @@
} }
}, },
"editCalendarView": { "editCalendarView": {
"description": "Mit deinem Token kannst du deinen HTWKalender jederzeit bearbeiten und sogar ins nächste Semester mitnehmen",
"error": "Fehler", "error": "Fehler",
"invalidToken": "Ungültiger Token", "invalidToken": "Ungültiger Token",
"headline": "Bearbeite deinen HTWKalender", "headline": "Bearbeite deinen HTWKalender",
@ -147,6 +151,7 @@
}, },
"faqView": { "faqView": {
"headline": "Fragen und Antworten", "headline": "Fragen und Antworten",
"description": "Falls du Fragen zum HTWKalender hast, findest du hier Antworten auf häufig gestellte Fragen und unsere Kontaktdaten.",
"firstQuestion": "Wie funktioniert das Kalender erstellen mit dem HTWKalender?", "firstQuestion": "Wie funktioniert das Kalender erstellen mit dem HTWKalender?",
"firstAnswer": "Die Webseite ermöglicht es deinen HTWK-Stundenplan in eines deiner bevorzugten Kalender-Verwaltungs-Programme (Outlook, Google Kalender, etc.) einzubinden. ", "firstAnswer": "Die Webseite ermöglicht es deinen HTWK-Stundenplan in eines deiner bevorzugten Kalender-Verwaltungs-Programme (Outlook, Google Kalender, etc.) einzubinden. ",
"secondQuestion": "Wie genau funktioniert das alles?", "secondQuestion": "Wie genau funktioniert das alles?",

View File

@ -6,6 +6,7 @@
"faq": "faq", "faq": "faq",
"imprint": "imprint", "imprint": "imprint",
"privacy": "privacy", "privacy": "privacy",
"description": "Your individual timetable with sports events and exams. Find upcoming events or free rooms for studying and working.",
"english": "English", "english": "English",
"german": "German", "german": "German",
"courseSelection": { "courseSelection": {
@ -22,6 +23,7 @@
"roomSchedule": "room occupancy", "roomSchedule": "room occupancy",
"headline": "room occupancy plan", "headline": "room occupancy plan",
"detail": "Please select a room to view the occupancy.", "detail": "Please select a room to view the occupancy.",
"description": "Would you like to quickly check whether a room is available? Here you can see the occupancy of the rooms at HTWK Leipzig.",
"dropDownSelect": "please select a room", "dropDownSelect": "please select a room",
"noRoomsAvailable": "no rooms listed", "noRoomsAvailable": "no rooms listed",
"available": "available", "available": "available",
@ -30,6 +32,7 @@
"freeRooms": { "freeRooms": {
"freeRooms": "free rooms", "freeRooms": "free rooms",
"detail": "Please select a time period to display rooms that have no occupancy.", "detail": "Please select a time period to display rooms that have no occupancy.",
"description": "Looking for a free study group room? Here you can see all available rooms at HTWK Leipzig in a specific period.",
"searchByRoom": "search by room", "searchByRoom": "search by room",
"pleaseSelectDate": "please select a date", "pleaseSelectDate": "please select a date",
"room": "room", "room": "room",
@ -68,6 +71,7 @@
} }
}, },
"editCalendarView": { "editCalendarView": {
"description": "With your token, you can edit your HTW calendar at any time and even take it with you into the next semester",
"error": "error", "error": "error",
"invalidToken": "invalid token", "invalidToken": "invalid token",
"headline": "edit your HTWKalender", "headline": "edit your HTWKalender",
@ -147,6 +151,7 @@
}, },
"faqView": { "faqView": {
"headline": "faq", "headline": "faq",
"description": "If you have any questions about the HTWKalender, you will find answers to frequently asked questions and our contact details here.",
"firstQuestion": "How does calendar creation work with HTWKalender?", "firstQuestion": "How does calendar creation work with HTWKalender?",
"firstAnswer": "The website allows you to integrate your HTWK timetable into one of your preferred calendar management programs (Outlook, Google Calendar, etc.).", "firstAnswer": "The website allows you to integrate your HTWK timetable into one of your preferred calendar management programs (Outlook, Google Calendar, etc.).",
"secondQuestion": "How does it all work exactly?", "secondQuestion": "How does it all work exactly?",

View File

@ -16,8 +16,7 @@
import "source-sans/source-sans-3.css"; import "source-sans/source-sans-3.css";
import { createApp } from "vue"; import { ViteSSG } from "vite-ssg";
import { createHead } from "@unhead/vue";
import "./style.css"; import "./style.css";
import App from "./App.vue"; import App from "./App.vue";
import PrimeVue from "primevue/config"; import PrimeVue from "primevue/config";
@ -36,7 +35,7 @@ import Slider from "primevue/slider";
import ToggleButton from "primevue/togglebutton"; import ToggleButton from "primevue/togglebutton";
import "primeicons/primeicons.css"; import "primeicons/primeicons.css";
import "primeflex/primeflex.css"; import "primeflex/primeflex.css";
import router from "./router"; import routes from "./router";
import SpeedDial from "primevue/speeddial"; import SpeedDial from "primevue/speeddial";
import TabView from "primevue/tabview"; import TabView from "primevue/tabview";
import TabPanel from "primevue/tabpanel"; import TabPanel from "primevue/tabpanel";
@ -57,57 +56,89 @@ import Skeleton from "primevue/skeleton";
import Calendar from "primevue/calendar"; import Calendar from "primevue/calendar";
import i18n from "./i18n"; import i18n from "./i18n";
import { VueQueryPlugin } from "@tanstack/vue-query"; import { VueQueryPlugin } from "@tanstack/vue-query";
import { Router } from "vue-router";
const app = createApp(App); var router : Router;
const pinia = createPinia();
app.use(VueQueryPlugin, { export const createApp = ViteSSG(
queryClientConfig: { App,
defaultOptions: { {
queries: { base: import.meta.env.BASE_URL,
refetchOnWindowFocus: false, routes: routes.routes,
},
},
}, },
}); (ctx) => {
const { app } = ctx;
const pinia = createPinia();
app.use(pinia);
const head = createHead(); router = ctx.router;
app.use(head);
app.use(PrimeVue); router.beforeEach(async (to, from) => {
app.use(router); if (import.meta.env.SSR) {
app.use(ToastService); return;
app.use(pinia); }
app.use(DialogService);
i18n.setup();
app.use(i18n.vueI18n);
app.component("Avatar", Avatar);
app.component("Badge", Badge);
app.component("Button", Button);
app.component("Menu", Menu);
app.component("Menubar", Menubar);
app.component("Dialog", Dialog);
app.component("Dropdown", Dropdown);
app.component("InputText", InputText);
app.component("InputSwitch", InputSwitch);
app.component("Card", Card);
app.component("DataView", DataView);
app.component("Slider", Slider);
app.component("ToggleButton", ToggleButton);
app.component("SpeedDial", SpeedDial);
app.component("TabView", TabView);
app.component("TabPanel", TabPanel);
app.component("MultiSelect", MultiSelect);
app.component("Tag", Tag);
app.component("Toast", Toast);
app.component("Accordion", Accordion);
app.component("AccordionTab", AccordionTab);
app.component("DataTable", DataTable);
app.component("Column", Column);
app.component("DynamicDialog", DynamicDialog);
app.component("ProgressSpinner", ProgressSpinner);
app.component("Checkbox", Checkbox);
app.component("Skeleton", Skeleton);
app.component("Calendar", Calendar);
app.mount("#app"); // External redirect
if (to.matched.some((record) => record.meta.redirect)) {
window.location.replace(to.meta.redirect as string);
return;
}
const newLocale = to.params.locale;
const prevLocale = from.params.locale;
// If the locale hasn't changed, do nothing
if (newLocale === prevLocale) {
return;
}
i18n.setLocale(newLocale);
});
app.use(VueQueryPlugin, {
queryClientConfig: {
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
},
});
app.use(router);
app.use(ToastService);
app.use(DialogService);
i18n.setup();
app.use(i18n.vueI18n);
app.use(PrimeVue);
app.component("Avatar", Avatar);
app.component("Badge", Badge);
app.component("Button", Button);
app.component("Menu", Menu);
app.component("Menubar", Menubar);
app.component("Dialog", Dialog);
app.component("Dropdown", Dropdown);
app.component("InputText", InputText);
app.component("InputSwitch", InputSwitch);
app.component("Card", Card);
app.component("DataView", DataView);
app.component("Slider", Slider);
app.component("ToggleButton", ToggleButton);
app.component("SpeedDial", SpeedDial);
app.component("TabView", TabView);
app.component("TabPanel", TabPanel);
app.component("MultiSelect", MultiSelect);
app.component("Tag", Tag);
app.component("Toast", Toast);
app.component("Accordion", Accordion);
app.component("AccordionTab", AccordionTab);
app.component("DataTable", DataTable);
app.component("Column", Column);
app.component("DynamicDialog", DynamicDialog);
app.component("ProgressSpinner", ProgressSpinner);
app.component("Checkbox", Checkbox);
app.component("Skeleton", Skeleton);
app.component("Calendar", Calendar);
},
)
export {router}

View File

@ -14,24 +14,22 @@
//You should have received a copy of the GNU Affero General Public License //You should have received a copy of the GNU Affero General Public License
//along with this program. If not, see <https://www.gnu.org/licenses/>. //along with this program. If not, see <https://www.gnu.org/licenses/>.
import { createRouter, createWebHistory } from "vue-router"; import { createMemoryHistory, RouterOptions, createWebHistory } from "vue-router";
const Faq = () => import("../components/FaqPage.vue");
const AdditionalModules = () => import("../view/AdditionalModules.vue");
const CalendarLink = () => import("../components/CalendarLink.vue");
const RenameModules = () => import("../components/RenameModules.vue");
const RoomFinder = () => import("../view/RoomFinder.vue");
const EditCalendarView = () => import("../view/EditCalendarView.vue");
const EditAdditionalModules = () =>
import("../view/editCalendar/EditAdditionalModules.vue");
const EditModules = () => import("../view/editCalendar/EditModules.vue");
const CourseSelection = () => import("../view/CourseSelection.vue"); const CourseSelection = () => import("../view/CourseSelection.vue");
const FreeRooms = () => import("../view/FreeRooms.vue"); const AdditionalModules = () => import("../view/create/AdditionalModules.vue");
const CalendarLink = () => import("../view/CalendarLink.vue");
const RenameModules = () => import("../view/create/RenameModules.vue");
const RoomFinder = () => import("../view/rooms/RoomFinder.vue");
const FreeRooms = () => import("../view/rooms/FreeRooms.vue");
const EditCalendarView = () => import("../view/edit/EditCalendar.vue");
const EditAdditionalModules = () =>
import("../view/edit/EditAdditionalModules.vue");
const EditModules = () => import("../view/edit/EditModules.vue");
const FaqView = () => import("../view/FaqView.vue");
import i18n from "../i18n"; const routes : RouterOptions = {
history: import.meta.env.SSR ? createMemoryHistory(import.meta.env.BASE_URL) : createWebHistory(import.meta.env.BASE_URL),
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [ routes: [
{ {
path: "/", path: "/",
@ -47,6 +45,7 @@ const router = createRouter({
component: RoomFinder, component: RoomFinder,
meta: { meta: {
label: "roomFinderPage.roomSchedule", label: "roomFinderPage.roomSchedule",
description: "roomFinderPage.description",
}, },
}, },
{ {
@ -55,14 +54,16 @@ const router = createRouter({
component: FreeRooms, component: FreeRooms,
meta: { meta: {
label: "freeRooms.freeRooms", label: "freeRooms.freeRooms",
} description: "freeRooms.description",
},
}, },
{ {
path: "/faq", path: "/faq",
name: "faq", name: "faq",
component: Faq, component: FaqView,
meta: { meta: {
label: "faq", label: "faq",
description: "faqView.description",
} }
}, },
{ {
@ -79,6 +80,7 @@ const router = createRouter({
component: EditAdditionalModules, component: EditAdditionalModules,
meta: { meta: {
label: "editCalendar", label: "editCalendar",
description: "editCalendarView.description"
} }
}, },
{ {
@ -87,6 +89,7 @@ const router = createRouter({
component: EditModules, component: EditModules,
meta: { meta: {
label: "editCalendar", label: "editCalendar",
description: "editCalendarView.description"
} }
}, },
{ {
@ -103,30 +106,25 @@ const router = createRouter({
component: EditCalendarView, component: EditCalendarView,
meta: { meta: {
label: "editCalendar", label: "editCalendar",
description: "editCalendarView.description"
} }
}, },
{ {
path: "/privacy-policy", path: "/privacy-policy",
name: "privacy-policy", name: "privacy-policy",
component: Faq, component: {},
beforeEnter() {
window.location.href =
"https://www.htwk-leipzig.de/hochschule/kontakt/datenschutzerklaerung/";
},
meta: { meta: {
label: "privacy" label: "privacy",
redirect: "https://www.htwk-leipzig.de/hochschule/kontakt/datenschutzerklaerung/",
} }
}, },
{ {
path: "/imprint", path: "/imprint",
name: "imprint", name: "imprint",
component: Faq, component: {},
beforeEnter() {
window.location.href =
"https://www.htwk-leipzig.de/hochschule/kontakt/impressum/";
},
meta: { meta: {
label: "imprint" label: "imprint",
redirect: "https://www.htwk-leipzig.de/hochschule/kontakt/impressum/",
} }
}, },
{ {
@ -138,16 +136,6 @@ const router = createRouter({
} }
}, },
], ],
}); };
router.beforeEach(async (to, from) => { export default routes;
const newLocale = to.params.locale;
const prevLocale = from.params.locale;
// If the locale hasn't changed, do nothing
if (newLocale === prevLocale) {
return;
}
i18n.setLocale(newLocale);
});
export default router;

View File

@ -20,7 +20,7 @@ import { useLocalStorage } from "@vueuse/core";
const localeStore = defineStore("localeStore", { const localeStore = defineStore("localeStore", {
state: () => { state: () => {
return { return {
locale: useLocalStorage("locale", "en"), //useLocalStorage takes in a key of 'count' and default value of 0 locale: useLocalStorage("locale", "de"), //useLocalStorage takes in a key of 'count' and default value of 0
}; };
}, },
actions: { actions: {

View File

@ -17,16 +17,16 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
--> -->
<script lang="ts" setup> <script lang="ts" setup>
import tokenStore from "../store/tokenStore.ts"; import tokenStore from "@/store/tokenStore.ts";
import { useToast } from "primevue/usetoast"; import { useToast } from "primevue/usetoast";
import { computed, onMounted } from "vue"; import { computed, inject, onMounted } from "vue";
import router from "../router"; import { router } from "@/main";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
const { t } = useI18n({ useScope: "global" }); const { t } = useI18n({ useScope: "global" });
const toast = useToast(); const toast = useToast();
const domain = window.location.hostname; const domain = inject<string>("domain")!;
const getLink = () => const getLink = () =>
"https://" + domain + "/api/feed?token=" + tokenStore().token; "https://" + domain + "/api/feed?token=" + tokenStore().token;

View File

@ -21,13 +21,13 @@ import { computed, ComputedRef, Ref, ref, watch } from "vue";
import { import {
fetchCourseBySemester, fetchCourseBySemester,
fetchModulesByCourseAndSemester, fetchModulesByCourseAndSemester,
} from "../api/fetchCourse"; } from "@/api/fetchCourse";
import DynamicPage from "./DynamicPage.vue"; import DynamicPage from "@/view/DynamicPage.vue";
import ModuleSelection from "../components/ModuleSelection.vue"; import ModuleSelection from "@/components/ModuleSelection.vue";
import { Module } from "../model/module.ts"; import { Module } from "@/model/module.ts";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import moduleStore from "../store/moduleStore"; import moduleStore from "@/store/moduleStore";
import router from "../router"; import { router } from "@/main";
async function getModules() { async function getModules() {
modules.value = await fetchModulesByCourseAndSemester( modules.value = await fetchModulesByCourseAndSemester(

View File

@ -42,7 +42,7 @@ const hasContent = computed(() => {
</script> </script>
<template> <template>
<heading class="flex flex-column align-items-center mt-0"> <div class="flex flex-column align-items-center mt-0">
<div <div
class="flex align-items-center justify-content-center gap-3 mx-2 mb-4 transition-rolldown" class="flex align-items-center justify-content-center gap-3 mx-2 mb-4 transition-rolldown"
:class="{ 'md:mt-8': hideContent }" :class="{ 'md:mt-8': hideContent }"
@ -87,7 +87,7 @@ const hasContent = computed(() => {
> >
<slot name="content"></slot> <slot name="content"></slot>
</div> </div>
</heading> </div>
</template> </template>
<style scoped> <style scoped>

View File

@ -17,9 +17,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
--> -->
<script lang="ts" setup> <script lang="ts" setup>
import moduleStore from "../store/moduleStore"; import moduleStore from "@/store/moduleStore";
import router from "../router"; import {router} from "@/main";
import AdditionalModuleTable from "../components/AdditionalModuleTable.vue"; import AdditionalModuleTable from "@/components/AdditionalModuleTable.vue";
const store = moduleStore(); const store = moduleStore();

View File

@ -17,13 +17,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
--> -->
<script setup lang="ts"> <script setup lang="ts">
import moduleStore from "../store/moduleStore.ts"; import moduleStore from "@/store/moduleStore.ts";
import { createIndividualFeed } from "../api/createFeed.ts"; import { createIndividualFeed } from "@/api/createFeed.ts";
import router from "../router"; import { router } from "@/main";
import tokenStore from "../store/tokenStore.ts"; import tokenStore from "@/store/tokenStore.ts";
import { Ref, computed, inject, ref, onMounted } from "vue"; import { Ref, computed, inject, ref, onMounted } from "vue";
import ModuleTemplateDialog from "./ModuleTemplateDialog.vue"; import ModuleTemplateDialog from "@/components/ModuleTemplateDialog.vue";
import { onlyWhitespace } from "../helpers/strings.ts"; import { onlyWhitespace } from "@/helpers/strings.ts";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { Module } from "@/model/module.ts"; import { Module } from "@/model/module.ts";
import { useToast } from "primevue/usetoast"; import { useToast } from "primevue/usetoast";

View File

@ -18,12 +18,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent } from "vue"; import { defineAsyncComponent } from "vue";
import moduleStore from "../../store/moduleStore"; import moduleStore from "@/store/moduleStore";
import router from "../../router"; import { router } from "@/main";
const store = moduleStore(); const store = moduleStore();
const AdditionalModuleTable = defineAsyncComponent( const AdditionalModuleTable = defineAsyncComponent(
() => import("../../components/AdditionalModuleTable.vue"), () => import("@/components/AdditionalModuleTable.vue"),
); );
async function nextStep() { async function nextStep() {

View File

@ -18,14 +18,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script setup lang="ts"> <script setup lang="ts">
import { Ref, ref } from "vue"; import { Ref, ref } from "vue";
import { Module } from "../model/module"; import { Module } from "@/model/module";
import moduleStore from "../store/moduleStore"; import moduleStore from "@/store/moduleStore";
import { getCalender } from "../api/loadCalendar"; import { getCalender } from "@/api/loadCalendar";
import router from "../router"; import { router } from "@/main";
import tokenStore from "../store/tokenStore"; import tokenStore from "@/store/tokenStore";
import { useToast } from "primevue/usetoast"; import { useToast } from "primevue/usetoast";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import DynamicPage from "./DynamicPage.vue"; import DynamicPage from "@/view/DynamicPage.vue";
const { t } = useI18n({ useScope: "global" }); const { t } = useI18n({ useScope: "global" });
const toast = useToast(); const toast = useToast();

View File

@ -19,12 +19,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, Ref, ref } from "vue"; import { computed, inject, Ref, ref } from "vue";
import { Module } from "@/model/module.ts"; import { Module } from "@/model/module.ts";
import moduleStore from "../../store/moduleStore"; import moduleStore from "@/store/moduleStore";
import { fetchAllModules } from "@/api/fetchCourse.ts"; import { fetchAllModules } from "@/api/fetchCourse.ts";
import { deleteIndividualFeed, saveIndividualFeed } from "@/api/createFeed.ts"; import { deleteIndividualFeed, saveIndividualFeed } from "@/api/createFeed.ts";
import tokenStore from "../../store/tokenStore"; import tokenStore from "@/store/tokenStore";
import router from "@/router"; import { router } from "@/main";
import ModuleTemplateDialog from "../../components/ModuleTemplateDialog.vue"; import ModuleTemplateDialog from "@/components/ModuleTemplateDialog.vue";
import { onlyWhitespace } from "@/helpers/strings.ts"; import { onlyWhitespace } from "@/helpers/strings.ts";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useToast } from "primevue/usetoast"; import { useToast } from "primevue/usetoast";

View File

@ -151,7 +151,7 @@ import DynamicPage from "@/view/DynamicPage.vue";
import { requestFreeRooms } from "@/api/requestFreeRooms.ts"; import { requestFreeRooms } from "@/api/requestFreeRooms.ts";
import { FilterMatchMode } from "primevue/api"; import { FilterMatchMode } from "primevue/api";
import { padStart } from "@fullcalendar/core/internal"; import { padStart } from "@fullcalendar/core/internal";
import router from "@/router"; import { router } from "@/main";
import { formatYearMonthDay } from "@/helpers/dates"; import { formatYearMonthDay } from "@/helpers/dates";
const mobilePage = inject("mobilePage") as Ref<boolean>; const mobilePage = inject("mobilePage") as Ref<boolean>;

View File

@ -18,11 +18,11 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
<script lang="ts" setup> <script lang="ts" setup>
import { Ref, computed, ref, watch } from "vue"; import { Ref, computed, ref, watch } from "vue";
import { fetchRoom } from "../api/fetchRoom.ts"; import { fetchRoom } from "@/api/fetchRoom.ts";
import DynamicPage from "./DynamicPage.vue"; import DynamicPage from "@/view/DynamicPage.vue";
import RoomOccupation from "../components/RoomOccupation.vue"; import RoomOccupation from "@/components/RoomOccupation.vue";
import { computedAsync } from "@vueuse/core"; import { computedAsync } from "@vueuse/core";
import router from "@/router"; import { router } from "@/main";
type Room = { type Room = {
name: string; name: string;

View File

@ -20,7 +20,8 @@
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"],
"primevue/*": ["./node_modules/primevue/*"]
} }
}, },
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],

View File

@ -18,7 +18,12 @@ import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue"; import vue from "@vitejs/plugin-vue";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import resolve from "@rollup/plugin-node-resolve"; import resolve from "@rollup/plugin-node-resolve";
import {resolve as pathResolver} from "path";
import terser from "@rollup/plugin-terser"; import terser from "@rollup/plugin-terser";
import ViteSSGOptions from "vite-ssg";
import generateSitemap from 'vite-ssg-sitemap'
const hostname = "https://cal.htwk-leipzig.de";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
@ -28,8 +33,27 @@ export default defineConfig({
terser(), terser(),
], ],
resolve: { resolve: {
alias: { alias:
"@": fileURLToPath(new URL("./src", import.meta.url)), {
"@": fileURLToPath(new URL("./src", import.meta.url)),
'primevue' : pathResolver(__dirname, 'node_modules/primevue'),
},
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.css', '.scss'],
},
ssgOptions: {
script: "async",
formatting: "minify",
format: "esm",
onFinished: () => {
generateSitemap({
hostname: hostname,
exclude: [
'/additional-modules',
'/edit-additional-modules',
'/edit-calendar',
'/rename-modules',
]
})
}, },
}, },
server: { server: {