commit
66c0861dd5
@ -1,3 +1,5 @@
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* I18n
|
||||
*
|
||||
@ -13,10 +15,15 @@
|
||||
|
||||
export class I18n {
|
||||
|
||||
translations = {};
|
||||
_translations = {};
|
||||
_datetimeLocale = undefined;
|
||||
|
||||
add(id, translation) {
|
||||
this.translations[id] = translation;
|
||||
if (!this._translations[id]) {
|
||||
this._translations[id] = translation;
|
||||
} else {
|
||||
throw new Error('I18N Error: Attempting to set translation multiple times for »' + id + '«!');
|
||||
}
|
||||
}
|
||||
|
||||
addMany(manyTranslations) {
|
||||
@ -24,9 +31,27 @@ export class I18n {
|
||||
}
|
||||
|
||||
get(id) {
|
||||
if (!this.translations[id]) {
|
||||
if (!this._translations[id]) {
|
||||
throw new Error('I18N Error: Translation missing for »' + id + '«!');
|
||||
}
|
||||
return this.translations[id];
|
||||
return this._translations[id];
|
||||
}
|
||||
|
||||
|
||||
setDatetimeLocale(locale) {
|
||||
if (!this._datetimeLocale) {
|
||||
moment.locale(locale);
|
||||
this._datetimeLocale = locale;
|
||||
} else {
|
||||
throw new Error('I18N Error: Attempting to set datetime locale multiple times!');
|
||||
}
|
||||
}
|
||||
|
||||
getDatetimeLocale() {
|
||||
if (!this._datetimeLocale) {
|
||||
throw new Error('I18N Error: Attempting to access datetime locale when it has not been set!');
|
||||
}
|
||||
|
||||
return this._datetimeLocale;
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ describe('I18n', () => {
|
||||
|
||||
// helper function
|
||||
function expectTranslation(id, value) {
|
||||
expect(i18n.translations[id]).toMatch(value);
|
||||
expect(i18n.get(id)).toMatch(value);
|
||||
}
|
||||
|
||||
it('should create', () => {
|
||||
@ -38,7 +38,7 @@ describe('I18n', () => {
|
||||
|
||||
describe('get()', () => {
|
||||
it('should return stored translations', () => {
|
||||
i18n.translations.id1 = 'something';
|
||||
i18n.add('id1', 'something');
|
||||
expect(i18n.get('id1')).toMatch('something');
|
||||
});
|
||||
|
||||
|
||||
@ -48,9 +48,6 @@ const DATEPICKER_CONFIG = {
|
||||
timeMinutes: 0,
|
||||
timeSeconds: 0,
|
||||
|
||||
// german settings
|
||||
// TODO: hardcoded, get from current language / settings
|
||||
locale: 'de',
|
||||
weekStart: 1,
|
||||
dateFormat: FORM_DATE_FORMAT_DATE_DT,
|
||||
timeFormat: FORM_DATE_FORMAT_TIME_DT,
|
||||
@ -86,6 +83,7 @@ export class Datepicker {
|
||||
datepickerInstance;
|
||||
_element;
|
||||
elementType;
|
||||
_locale;
|
||||
|
||||
constructor(element) {
|
||||
if (!element) {
|
||||
@ -96,6 +94,8 @@ export class Datepicker {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._locale = window.App.i18n.getDatetimeLocale();
|
||||
|
||||
// initialize datepickerCollections singleton if not already done
|
||||
if (!Datepicker.datepickerCollections) {
|
||||
Datepicker.datepickerCollections = new Map();
|
||||
@ -134,7 +134,7 @@ export class Datepicker {
|
||||
}
|
||||
|
||||
// initialize tail.datetime (datepicker) instance and let it do weird stuff with the element value
|
||||
this.datepickerInstance = datetime(this._element, { ...datepickerGlobalConfig, ...datepickerConfig });
|
||||
this.datepickerInstance = datetime(this._element, { ...datepickerGlobalConfig, ...datepickerConfig, locale: this._locale });
|
||||
|
||||
// reset date to something sane
|
||||
if (parsedMomentDate)
|
||||
|
||||
@ -5,7 +5,7 @@ var CHECKBOX_CLASS = 'checkbox';
|
||||
var CHECKBOX_INITIALIZED_CLASS = 'checkbox--initialized';
|
||||
|
||||
@Utility({
|
||||
selector: 'input[type="checkbox"]',
|
||||
selector: 'input[type="checkbox"]:not([uw-no-checkbox])',
|
||||
})
|
||||
export class Checkbox {
|
||||
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
|
||||
/* CUSTOM CHECKBOXES */
|
||||
/* Completely replaces legacy checkbox */
|
||||
.checkbox [type='checkbox'], #lang-checkbox {
|
||||
position: fixed;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
[type='checkbox'] {
|
||||
position: fixed;
|
||||
top: -1px;
|
||||
left: -1px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
height: 20px;
|
||||
|
||||
@ -150,7 +150,6 @@ textarea {
|
||||
padding: 4px 13px;
|
||||
font-size: 1rem;
|
||||
font-family: var(--font-base);
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
border: 1px solid #dbdbdb;
|
||||
border-radius: 2px;
|
||||
@ -184,8 +183,8 @@ textarea {
|
||||
|
||||
/* OPTIONS */
|
||||
|
||||
select {
|
||||
-webkit-appearance: menulist;
|
||||
select[size = "1"], select:not([size]) {
|
||||
appearance: menulist;
|
||||
}
|
||||
|
||||
select,
|
||||
|
||||
48
frontend/src/utils/navbar/navbar.js
Normal file
48
frontend/src/utils/navbar/navbar.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { Utility } from '../../core/utility';
|
||||
import './navbar.scss';
|
||||
|
||||
|
||||
export const LANGUAGE_SELECT_UTIL_SELECTOR = '[uw-language-select]';
|
||||
const LANGUAGE_SELECT_INITIALIZED_CLASS = 'language-select--initialized';
|
||||
|
||||
|
||||
@Utility({
|
||||
selector: LANGUAGE_SELECT_UTIL_SELECTOR,
|
||||
})
|
||||
export class LanguageSelectUtil {
|
||||
_element;
|
||||
checkbox;
|
||||
|
||||
constructor(element) {
|
||||
if (!element) {
|
||||
throw new Error('Language Select utility needs to be passed an element!');
|
||||
}
|
||||
|
||||
if (element.classList.contains(LANGUAGE_SELECT_INITIALIZED_CLASS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._element = element;
|
||||
this.checkbox = element.querySelector('#lang-checkbox');
|
||||
|
||||
window.addEventListener('click', event => this.close(event));
|
||||
|
||||
element.classList.add(LANGUAGE_SELECT_INITIALIZED_CLASS);
|
||||
}
|
||||
|
||||
close(event) {
|
||||
if (!this._element.contains(event.target) && window.document.contains(event.target)) {
|
||||
this.checkbox.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const NavbarUtils = [
|
||||
LanguageSelectUtil,
|
||||
];
|
||||
@ -68,14 +68,7 @@
|
||||
color: var(--color-lightwhite);
|
||||
transition: height .2s cubic-bezier(0.03, 0.43, 0.58, 1);
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-lightwhite);
|
||||
|
||||
.navbar__link-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.navbar__link-icon {
|
||||
@ -88,6 +81,7 @@
|
||||
transition: opacity .2s ease;
|
||||
padding: 2px 4px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
@ -146,7 +140,9 @@
|
||||
.navbar__list-item {
|
||||
position: relative;
|
||||
transition: background-color .1s ease;
|
||||
|
||||
&:not(.navbar__list-item--favorite) + .navbar__list-item--lang-wrapper {
|
||||
margin-left: 12px;
|
||||
}
|
||||
&:not(.navbar__list-item--favorite) + .navbar__list-item {
|
||||
margin-left: 12px;
|
||||
}
|
||||
@ -160,6 +156,9 @@
|
||||
&:not(.navbar__list-item--favorite) + .navbar__list-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
&:not(.navbar__list-item--favorite) + .navbar__list-item--lang-wrapper {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,9 +218,13 @@
|
||||
color: var(--color-dark);
|
||||
}
|
||||
|
||||
.navbar .navbar__list-item:not(.navbar__list-item--active):not(.navbar__list-item--favorite):hover .navbar__link-wrapper {
|
||||
.navbar .navbar__list-item:not(.navbar__list-item--active):not(.navbar__list-item--favorite):hover .navbar__link-wrapper, #lang-checkbox:checked ~ * .navbar__link-wrapper {
|
||||
background-color: var(--color-dark);
|
||||
color: var(--color-lightwhite);
|
||||
|
||||
.navbar__link-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* sticky state */
|
||||
@ -267,3 +270,36 @@
|
||||
height: var(--header-height-collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#lang-dropdown {
|
||||
display: none;
|
||||
|
||||
position: fixed;
|
||||
top: var(--header-height);
|
||||
right: 0;
|
||||
min-width: 200px;
|
||||
z-index: 10;
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 10px rgba(0,0,0,0.3);
|
||||
|
||||
select {
|
||||
display: block;
|
||||
}
|
||||
|
||||
button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#lang-checkbox:checked ~ #lang-dropdown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#lang-dropdown {
|
||||
top: var(--header-height-collapsed);
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,7 @@ import { MassInput } from './mass-input/mass-input';
|
||||
import { Modal } from './modal/modal';
|
||||
import { Tooltip } from './tooltips/tooltips';
|
||||
import { CourseTeaser } from './course-teaser/course-teaser';
|
||||
import { NavbarUtils } from './navbar/navbar';
|
||||
|
||||
export const Utils = [
|
||||
Alerts,
|
||||
@ -25,4 +26,5 @@ export const Utils = [
|
||||
ShowHide,
|
||||
Tooltip,
|
||||
CourseTeaser,
|
||||
...NavbarUtils,
|
||||
];
|
||||
|
||||
3
messages/button/en.msg
Normal file
3
messages/button/en.msg
Normal file
@ -0,0 +1,3 @@
|
||||
AmbiguousButtons: Multiple active submit buttons
|
||||
WrongButtonValue: Submit button has wrong value
|
||||
MultipleButtonValues: Submit button has multiple values
|
||||
@ -1,5 +1,6 @@
|
||||
CampusIdentPlaceholder: Vorname.Nachname@campus.lmu.de
|
||||
CampusIdent: Campus-Kennung
|
||||
CampusPassword: Passwort
|
||||
CampusPasswordPlaceholder: Passwort
|
||||
CampusSubmit: Abschicken
|
||||
CampusInvalidCredentials: Ungültige Logindaten
|
||||
6
messages/campus/en.msg
Normal file
6
messages/campus/en.msg
Normal file
@ -0,0 +1,6 @@
|
||||
CampusIdentPlaceholder: First.Last@campus.lmu.de
|
||||
CampusIdent: Campus account
|
||||
CampusPassword: Password
|
||||
CampusPasswordPlaceholder: Password
|
||||
CampusSubmit: Send
|
||||
CampusInvalidCredentials: Invalid login
|
||||
@ -1,2 +1,3 @@
|
||||
DummyIdent: Nutzer-Kennung
|
||||
DummyIdent: Identifikation
|
||||
DummyIdentPlaceholder: Identifikation
|
||||
DummyNoFormData: Keine Formulardaten empfangen
|
||||
3
messages/dummy/en.msg
Normal file
3
messages/dummy/en.msg
Normal file
@ -0,0 +1,3 @@
|
||||
DummyIdent: Identification
|
||||
DummyIdentPlaceholder: Identification
|
||||
DummyNoFormData: No form data received
|
||||
@ -1,4 +1,4 @@
|
||||
FilesSelected: Dateien ausgewählt
|
||||
SelectFile: Datei auswählen
|
||||
SelectFiles: Datei(en) auswählen
|
||||
AsyncFormFailure: Da ist etwas schief gelaufen, das tut uns Leid. Falls das erneut passiert schicke uns gerne eine kurze Beschreibung dieses Ereignisses über das Hilfe-Widget rechts oben. Vielen Dank für deine Hilfe!
|
||||
AsyncFormFailure: Da ist etwas schief gelaufen, das tut uns Leid. Falls das erneut passiert schicken Sie uns bitte eine kurze Beschreibung dieses Ereignisses über das Hilfe-Widget rechts oben. Vielen Dank für Ihre Hilfe!
|
||||
4
messages/frontend/en.msg
Normal file
4
messages/frontend/en.msg
Normal file
@ -0,0 +1,4 @@
|
||||
FilesSelected: Files selected
|
||||
SelectFile: Select file
|
||||
SelectFiles: Select file(s)
|
||||
AsyncFormFailure: Something went wrong, we are sorry. If this error occurs again, please let us know by clicking the Support button in the upper right corner. Thank you very much!
|
||||
@ -1,2 +1,4 @@
|
||||
PWHashIdent: Identifikation
|
||||
PWHashPassword: Passwort
|
||||
PWHashIdentPlaceholder: Identifikation
|
||||
PWHashPassword: Passwort
|
||||
PWHashPasswordPlaceholder: Passwort
|
||||
4
messages/pw-hash/en.msg
Normal file
4
messages/pw-hash/en.msg
Normal file
@ -0,0 +1,4 @@
|
||||
PWHashIdent: Identification
|
||||
PWHashIdentPlaceholder: Identification
|
||||
PWHashPassword: Password
|
||||
PWHashPasswordPlaceholder: Password
|
||||
@ -1,5 +1,7 @@
|
||||
PrintDebugForStupid name@Text: Debug message "#{name}"
|
||||
|
||||
Logo: Uni2work
|
||||
|
||||
BtnSubmit: Senden
|
||||
BtnAbort: Abbrechen
|
||||
BtnDelete: Löschen
|
||||
@ -71,9 +73,9 @@ Term: Semester
|
||||
TermPlaceholder: W/S + vierstellige Jahreszahl
|
||||
|
||||
TermStartDay: Erster Tag
|
||||
TermStartDayTooltip: Üblicherweise immer 1.April oder 1.Oktober
|
||||
TermStartDayTooltip: Üblicherweise immer 1. April oder 1. Oktober
|
||||
TermEndDay: Letzter Tag
|
||||
TermEndDayTooltip: Üblicherweise immer 30.September oder 31.März
|
||||
TermEndDayTooltip: Üblicherweise immer 30. September oder 31. März
|
||||
TermHolidays: Feiertage
|
||||
TermHolidayPlaceholder: Feiertag
|
||||
TermLectureStart: Beginn Vorlesungen
|
||||
@ -98,6 +100,7 @@ CourseRegistration: Kursanmeldung
|
||||
CourseRegisterOpen: Anmeldung möglich
|
||||
CourseRegisterOk: Erfolgreich zum Kurs angemeldet
|
||||
CourseDeregisterOk: Erfolgreich vom Kurs abgemeldet
|
||||
CourseApply: Zum Kurs bewerben
|
||||
CourseApplyOk: Erfolgreich zum Kurs beworben
|
||||
CourseRetractApplyOk: Bewerbung zum Kurs erfolgreich zurückgezogen
|
||||
CourseDeregisterLecturerTip: Wenn Sie den Teilnehmer vom Kurs abmelden kann es sein, dass sie Zugriff auf diese Daten verlieren
|
||||
@ -109,8 +112,8 @@ CourseTutorial: Tutorium
|
||||
CourseSecretWrong: Falsches Passwort
|
||||
CourseSecret: Zugangspasswort
|
||||
CourseEditOk tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} wurde erfolgreich geändert.
|
||||
CourseNewDupShort tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} konnte nicht erstellt werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{csh} in diesem Semester.
|
||||
CourseEditDupShort tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} konnte nicht geändert werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{csh} in diesem Semester.
|
||||
CourseNewDupShort tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} konnte nicht erstellt werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{csh} in diesem Semester und Institut.
|
||||
CourseEditDupShort tid@TermId ssh@SchoolId csh@CourseShorthand: Kurs #{tid}-#{ssh}-#{csh} konnte nicht geändert werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{csh} in diesem Semester und Institut.
|
||||
FFSheetName: Name
|
||||
TermCourseListHeading tid@TermId: Kursübersicht #{tid}
|
||||
TermSchoolCourseListHeading tid@TermId school@SchoolName: Kursübersicht #{tid} für #{school}
|
||||
@ -128,7 +131,7 @@ CourseMembersCountLimited n@Int max@Int: #{n}/#{max}
|
||||
CourseMembersCountOf n@Int mbNum@IntMaybe: #{n} Kursanmeldungen #{maybeToMessage " von " mbNum " möglichen"}
|
||||
CourseName: Name
|
||||
CourseDescription: Beschreibung
|
||||
CourseDescriptionTip: Beliebiges HTML-Markup ist gestattet
|
||||
CourseDescriptionTip: Beliebiges Html-Markup ist gestattet
|
||||
CourseHomepageExternal: Externe Homepage
|
||||
CourseShorthand: Kürzel
|
||||
CourseShorthandUnique: Muss nur innerhalb Institut und Semester eindeutig sein. Wird verbatim in die Url der Kursseite übernommen.
|
||||
@ -142,14 +145,16 @@ CourseRegisterToTip: Darf auch unbegrenzt offen bleiben
|
||||
CourseDeregisterUntilTip: Abmeldung ist ab "Anmeldungen von" bis zu diesem Zeitpunkt erlaubt. Die Abmeldung darf auch unbegrenzt erlaubt bleiben.
|
||||
CourseFilterSearch: Volltext-Suche
|
||||
CourseFilterRegistered: Registriert
|
||||
CourseFilterNone: Egal
|
||||
CourseFilterNone: —
|
||||
BoolIrrelevant: —
|
||||
CourseDeleteQuestion: Wollen Sie den unten aufgeführten Kurs wirklich löschen?
|
||||
CourseDeleted: Kurs gelöscht
|
||||
CourseUserTutorials: Angemeldete Tutorien
|
||||
CourseUserNote: Notiz
|
||||
CourseUserNoteTooltip: Nur für Dozenten dieses Kurses einsehbar
|
||||
CourseUserNoteTooltip: Nur für Verwalter dieses Kurses einsehbar
|
||||
CourseUserNoteSaved: Notizänderungen gespeichert
|
||||
CourseUserNoteDeleted: Teilnehmernotiz gelöscht
|
||||
CourseUserRegister: Zum Kurs anmelden
|
||||
CourseUserDeregister: Vom Kurs abmelden
|
||||
CourseUsersDeregistered count@Int64: #{show count} Teilnehmer vom Kurs abgemeldet
|
||||
CourseUserRegisterTutorial: Zu einem Tutorium anmelden
|
||||
@ -184,7 +189,7 @@ CourseApplication: Bewerbung
|
||||
CourseApplicationIsParticipant: Kursteilnehmer
|
||||
|
||||
CourseApplicationExists: Sie haben sich bereits für diesen Kurs beworben
|
||||
CourseApplicationInvalidAction: Angegeben Aktion kann nicht durchgeführt werden
|
||||
CourseApplicationInvalidAction: Angegebene Aktion kann nicht durchgeführt werden
|
||||
CourseApplicationCreated csh@CourseShorthand: Erfolgreich zu #{csh} beworben
|
||||
CourseApplicationEdited csh@CourseShorthand: Bewerbung zu #{csh} erfolgreich angepasst
|
||||
CourseApplicationNotEdited csh@CourseShorthand: Bewerbung zu #{csh} hat sich nicht verändert
|
||||
@ -271,6 +276,8 @@ SheetSubmissionMode: Abgabe-Modus
|
||||
SheetExercise: Aufgabenstellung
|
||||
SheetHint: Hinweis
|
||||
SheetHintFrom: Hinweis ab
|
||||
SheetHintFromPlaceholder: Datum, sonst nur für Korrektoren
|
||||
SheetSolutionFromPlaceholder: Datum, sonst nur für Korrektoren
|
||||
SheetSolution: Lösung
|
||||
SheetSolutionFrom: Lösung ab
|
||||
SheetMarking: Hinweise für Korrektoren
|
||||
@ -478,7 +485,7 @@ HomeOpenAllocations: Offene Zentralanmeldungen
|
||||
HomeUpcomingSheets: Anstehende Übungsblätter
|
||||
HomeUpcomingExams: Bevorstehende Prüfungen
|
||||
|
||||
NumCourses num@Int64: #{num} Kurse
|
||||
NumCourses num@Int64: #{num} #{pluralDE num "Kurs" "Kurse"}
|
||||
CloseAlert: Schliessen
|
||||
|
||||
Name: Name
|
||||
@ -519,15 +526,15 @@ NatField name@Text: #{name} muss eine natürliche Zahl sein!
|
||||
JSONFieldDecodeFailure aesonFailure@String: Konnte JSON nicht parsen: #{aesonFailure}
|
||||
SecretJSONFieldDecryptFailure: Konnte versteckte vertrauliche Daten nicht entschlüsseln
|
||||
|
||||
SubmissionsAlreadyAssigned num@Int64: #{num} Abgaben waren bereits einem Korrektor zugeteilt und wurden nicht verändert:
|
||||
SubmissionsAssignUnauthorized num@Int64: #{num} Abgaben können momentan nicht einem Korrektor zugeteilt werden (z.B. weil die Abgabe noch offen ist):
|
||||
UpdatedAssignedCorrectorSingle num@Int64: #{num} Abgaben wurden dem neuen Korrektor zugeteilt.
|
||||
SubmissionsAlreadyAssigned num@Int64: #{num} #{pluralDE num "Abgabe" "Abgaben"} waren bereits einem Korrektor zugeteilt und wurden nicht verändert:
|
||||
SubmissionsAssignUnauthorized num@Int64: #{num} #{pluralDE num "Abgabe" "Abgaben"} können momentan nicht einem Korrektor zugeteilt werden (z.B. weil die Abgabe noch offen ist):
|
||||
UpdatedAssignedCorrectorSingle num@Int64: #{num} #{pluralDE num "Abgabe" "Abgaben"} wurden dem neuen Korrektor zugeteilt.
|
||||
NoCorrector: Kein Korrektor
|
||||
RemovedCorrections num@Int64: Korrektur-Daten wurden von #{num} Abgaben entfernt.
|
||||
UpdatedAssignedCorrectorsAuto num@Int64: #{num} Abgaben wurden unter den Korrektoren aufgeteilt.
|
||||
RemovedCorrections num@Int64: Korrektur-Daten wurden von #{num} #{pluralDE num "Abgabe" "Abgaben"} entfernt.
|
||||
UpdatedAssignedCorrectorsAuto num@Int64: #{num} #{pluralDE num "Abgabe" "Abgaben"} wurden unter den Korrektoren aufgeteilt.
|
||||
UpdatedSheetCorrectorsAutoAssigned n@Int: #{n} #{pluralDE n "Abgabe wurde einem Korrektor" "Abgaben wurden Korrektoren"} zugteilt.
|
||||
UpdatedSheetCorrectorsAutoFailed n@Int: #{n} #{pluralDE n "Abgabe konnte" "Abgaben konnten"} nicht automatisch zugewiesen werden.
|
||||
CouldNotAssignCorrectorsAuto num@Int64: #{num} Abgaben konnten nicht automatisch zugewiesen werden:
|
||||
CouldNotAssignCorrectorsAuto num@Int64: #{num} #{pluralDE num "Abgabe konnte" "Abgaben konnten"} nicht automatisch zugewiesen werden:
|
||||
SelfCorrectors num@Int64: #{num} Abgaben wurden Abgebenden als eigenem Korrektor zugeteilt!
|
||||
|
||||
SubmissionOriginal: Original
|
||||
@ -604,20 +611,21 @@ RatingNotUnicode uexc@UnicodeException: Bewertungsdatei nicht in UTF-8 kodiert:
|
||||
RatingMissingSeparator: Präambel der Bewertungsdatei konnte nicht identifziert werden
|
||||
RatingMultiple: Bewertungen enthält mehrere Punktzahlen für die gleiche Abgabe
|
||||
RatingInvalid parseErr@Text: Bewertungspunktzahl konnte nicht als Zahl verstanden werden: #{parseErr}
|
||||
RatingFileIsDirectory: Unerwarteter Fehler: Datei ist unerlaubterweise ein Verzeichnis
|
||||
RatingFileIsDirectory: Bewertungsdatei ist unerlaubterweise ein Verzeichnis
|
||||
RatingNegative: Bewertungspunkte dürfen nicht negativ sein
|
||||
RatingExceedsMax: Bewertung übersteigt die erlaubte Maximalpunktzahl
|
||||
RatingNotExpected: Keine Bewertungen erlaubt
|
||||
RatingBinaryExpected: Bewertung muss 0 (=durchgefallen) oder 1 (=bestanden) sein
|
||||
RatingPointsRequired: Bewertung erfordert für dieses Blatt eine Punktzahl
|
||||
RatingFile: Bewertungsdatei
|
||||
|
||||
SubmissionSinkExceptionDuplicateFileTitle file@FilePath: Dateiname #{show file} kommt mehrfach im Zip-Archiv vor
|
||||
SubmissionSinkExceptionDuplicateRating: Mehr als eine Bewertung gefunden.
|
||||
SubmissionSinkExceptionRatingWithoutUpdate: Bewertung gefunden, es ist hier aber keine Bewertung der Abgabe möglich.
|
||||
SubmissionSinkExceptionForeignRating smid@CryptoFileNameSubmission: Fremde Bewertung für Abgabe #{toPathPiece smid} enthalten. Bewertungen müssen sich immer auf die gleiche Abgabe beziehen!
|
||||
SubmissionSinkExceptionInvalidFileTitleExtension file@FilePath: Dateiname #{show file} hat keine der für dieses Übungsblatt zulässigen Dateiendungen.
|
||||
SubmissionSinkExceptionInvalidFileTitleExtension file@FilePath: Dateiname „#{show file}“ hat keine der für dieses Übungsblatt zulässigen Dateiendungen.
|
||||
|
||||
MultiSinkException name@Text error@Text: In Abgabe #{name} ist ein Fehler aufgetreten: #{error}
|
||||
MultiSinkException name@Text error@Text: In Abgabe „#{name}“ ist ein Fehler aufgetreten: #{error}
|
||||
|
||||
NoTableContent: Kein Tabelleninhalt
|
||||
NoUpcomingSheetDeadlines: Keine anstehenden Übungsblätter
|
||||
@ -627,7 +635,7 @@ AdminHeading: Administration
|
||||
AdminUserHeading: Benutzeradministration
|
||||
AdminUserRightsHeading: Benutzerrechte
|
||||
AdminUserAuthHeading: Benutzer-Authentifizierung
|
||||
AdminUserHeadingFor: Benuterprofil für
|
||||
AdminUserHeadingFor: Benutzerprofil für
|
||||
AdminFor: Administrator
|
||||
LecturerFor: Dozent
|
||||
LecturersFor: Dozenten
|
||||
@ -635,7 +643,6 @@ AssistantFor: Assistent
|
||||
AssistantsFor: Assistenten
|
||||
TutorsFor n@Int: #{pluralDE n "Tutor" "Tutoren"}
|
||||
CorrectorsFor n@Int: #{pluralDE n "Korrektor" "Korrektoren"}
|
||||
ForSchools n@Int: für #{pluralDE n "Institut" "Institute"}
|
||||
UserListTitle: Komprehensive Benutzerliste
|
||||
AccessRightsSaved: Berechtigungen erfolgreich verändert
|
||||
AccessRightsNotChanged: Berechtigungen wurden nicht verändert
|
||||
@ -647,7 +654,7 @@ DateTimeFormat: Datums- und Uhrzeitformat
|
||||
DateFormat: Datumsformat
|
||||
TimeFormat: Uhrzeitformat
|
||||
DownloadFiles: Dateien automatisch herunterladen
|
||||
DownloadFilesTip: Wenn gesetzt werden Dateien von Abgaben und Übungsblättern automatisch als Download behandelt, ansonsten ist das Verhalten browserabhängig (es können z.B. PDFs im Browser geöffnet werden).
|
||||
DownloadFilesTip: Wenn gesetzt werden Dateien automatisch als Download behandelt, ansonsten ist das Verhalten browserabhängig (es können z.B. PDFs im Browser geöffnet werden).
|
||||
WarningDays: Fristen-Vorschau
|
||||
WarningDaysTip: Wie viele Tage im Voraus sollen Fristen von Klausuren etc. auf Ihrer Startseite angezeigt werden?
|
||||
NotificationSettings: Erwünschte Benachrichtigungen
|
||||
@ -659,6 +666,10 @@ FormCosmetics: Oberfläche
|
||||
FormPersonalAppearance: Öffentliche Daten
|
||||
FormFieldRequiredTip: Gekennzeichnete Pflichtfelder sind immer auszufüllen
|
||||
|
||||
PersonalInfoExamAchievementsWip: Die Anzeige von Prüfungsergebnissen wird momentan an dieser Stelle leider noch nicht unterstützt.
|
||||
PersonalInfoOwnTutorialsWip: Die Anzeige von Tutorien, zu denen Sie als Tutor eingetragen sind wird momentan an dieser Stelle leider noch nicht unterstützt.
|
||||
PersonalInfoTutorialsWip: Die Anzeige von Tutorien, zu denen Sie angemeldet sind wird momentan an dieser Stelle leider noch nicht unterstützt.
|
||||
|
||||
ActiveAuthTags: Aktivierte Authorisierungsprädikate
|
||||
|
||||
InvalidDateTimeFormat: Ungültiges Datums- und Zeitformat, JJJJ-MM-TTTHH:MM[:SS] Format erwartet
|
||||
@ -725,8 +736,8 @@ UploadSpecificFileRequired: Zur Abgabe erforderlich
|
||||
|
||||
NoSubmissions: Keine Abgabe
|
||||
CorrectorSubmissions: Abgabe extern mit Pseudonym
|
||||
UserSubmissions: Direkte Abgabe
|
||||
BothSubmissions: Abgabe direkt & extern mit Pseudonym
|
||||
UserSubmissions: Direkte Abgabe in Uni2work
|
||||
BothSubmissions: Abgabe direkt in Uni2work & extern mit Pseudonym
|
||||
|
||||
SheetCorrectorSubmissionsTip: Abgabe erfolgt über ein Uni2work-externes Verfahren (zumeist in Papierform durch Einwurf) unter Angabe eines persönlichen Pseudonyms. Korrektoren können mithilfe des Pseudonyms später Korrekturergebnisse in Uni2work eintragen, damit Sie sie einsehen können.
|
||||
|
||||
@ -738,8 +749,10 @@ SubmissionUpdated: Abgabe erfolgreich ersetzt
|
||||
AdminFeaturesHeading: Studiengänge
|
||||
StudyTerms: Studiengänge
|
||||
StudyTerm: Studiengang
|
||||
NoStudyTermsKnown: Nicht eingeschrieben
|
||||
NoStudyTermsKnown: Keine Studiengänge bekannt
|
||||
StudyFeatureInference: Studiengangschlüssel-Inferenz
|
||||
StudyFeatureInferenceNoConflicts: Keine Konflikte beobachtet
|
||||
StudyFeatureInferenceConflictsHeading: Studiengangseinträge mit beobachteten Konflikten
|
||||
StudyFeatureAge: Fachsemester
|
||||
StudyFeatureDegree: Abschluss
|
||||
FieldPrimary: Hauptfach
|
||||
@ -757,9 +770,9 @@ DegreeShort: Abschlusskürzel
|
||||
StudyTermsKey: Studiengangschlüssel
|
||||
StudyTermsName: Studiengang
|
||||
StudyTermsShort: Studiengangkürzel
|
||||
StudyTermsChangeSuccess: Zuordnung Abschlüsse aktualisiert
|
||||
StudyDegreeChangeSuccess: Zuordnung Studiengänge aktualisiert
|
||||
StudyCandidateIncidence: Anmeldevorgang
|
||||
StudyTermsChangeSuccess: Zuordnung Studiengänge aktualisiert
|
||||
StudyDegreeChangeSuccess: Zuordnung Abschlüsse aktualisiert
|
||||
StudyCandidateIncidence: Synchronisation
|
||||
AmbiguousCandidatesRemoved n@Int: #{show n} #{pluralDE n "uneindeutiger Kandidat" "uneindeutige Kandiaten"} entfernt
|
||||
RedundantCandidatesRemoved n@Int: #{show n} bereits #{pluralDE n "bekannter Kandidat" "bekannte Kandiaten"} entfernt
|
||||
CandidatesInferred n@Int: #{show n} neue #{pluralDE n "Studiengangszuordnung" "Studiengangszuordnungen"} inferiert
|
||||
@ -778,6 +791,8 @@ MailTestDateTime: Test der Datumsformattierung:
|
||||
|
||||
German: Deutsch
|
||||
GermanGermany: Deutsch (Deutschland)
|
||||
English: Englisch
|
||||
EnglishEurope: Englisch (Europa)
|
||||
|
||||
MailSubjectSubmissionRated csh@CourseShorthand: Ihre #{csh}-Abgabe wurde korrigiert
|
||||
MailSubmissionRatedIntro courseName@Text termDesc@Text: Ihre Abgabe im Kurs #{courseName} (#{termDesc}) wurde korrigiert.
|
||||
@ -859,9 +874,9 @@ MailSubjectExamOfficeUserInvitation displayName@Text: Berücksichtigung von Prü
|
||||
MailSubjectPasswordReset: Uni2work-Passwort ändern bzw. setzen
|
||||
|
||||
SheetGrading: Bewertung
|
||||
SheetGradingPoints maxPoints@Points: #{maxPoints} Punkte
|
||||
SheetGradingPassPoints maxPoints@Points passingPoints@Points: Bestanden ab #{passingPoints} von #{maxPoints} Punkten
|
||||
SheetGradingPassBinary: Bestanden/Nicht Bestanden
|
||||
SheetGradingPoints maxPoints@Points: #{maxPoints} #{pluralDE maxPoints "Punkt" "Punkte"}
|
||||
SheetGradingPassPoints maxPoints@Points passingPoints@Points: Bestanden ab #{passingPoints} von #{maxPoints} #{pluralDE maxPoints "Punkt" "Punkten"}
|
||||
SheetGradingPassBinary: Bestanden/Nicht Bestanden
|
||||
SheetGradingInfo: "Bestanden nach Punkten" zählt sowohl zur maximal erreichbaren Gesamtpunktzahl also auch zur Anzahl der zu bestehenden Blätter.
|
||||
|
||||
SheetGradingCount': Anzahl
|
||||
@ -915,10 +930,10 @@ NotificationTriggerExamRegistrationSoonInactive: Ich kann mich bald nicht mehr f
|
||||
NotificationTriggerExamDeregistrationSoonInactive: Ich kann mich bald nicht mehr von einer Prüfung abmelden
|
||||
NotificationTriggerExamResult: Ich kann ein neues Prüfungsergebnis einsehen
|
||||
NotificationTriggerAllocationStaffRegister: Ich kann Kurse bei einer neuen Zentralanmeldung eintragen
|
||||
NotificationTriggerAllocationAllocation: Ich kann Zentralanmeldung-Bewerbungen für einen meiner Kurse bewerten
|
||||
NotificationTriggerAllocationAllocation: Ich kann Zentralanmeldungs-Bewerbungen für einen meiner Kurse bewerten
|
||||
NotificationTriggerAllocationRegister: Ich kann mich bei einer neuen Zentralanmeldung bewerben
|
||||
NotificationTriggerAllocationOutdatedRatings: Zentralanmeldung-Bewerbungen für einen meiner Kurse wurden verändert, nachdem sie bewertet wurden
|
||||
NotificationTriggerAllocationUnratedApplications: Bewertungen zu Zentralanmeldung-Bewerbungen für einen meiner Kurse stehen aus
|
||||
NotificationTriggerAllocationOutdatedRatings: Zentralanmeldungs-Bewerbungen für einen meiner Kurse wurden verändert, nachdem sie bewertet wurden
|
||||
NotificationTriggerAllocationUnratedApplications: Bewertungen zu Zentralanmeldungs-Bewerbungen für einen meiner Kurse stehen aus
|
||||
NotificationTriggerAllocationResults: Plätze wurden für eine meiner Zentralanmeldungen verteilt
|
||||
NotificationTriggerExamOfficeExamResults: Ich kann neue Prüfungsergebnisse einsehen
|
||||
NotificationTriggerExamOfficeExamResultsChanged: Prüfungsergebnisse wurden verändert
|
||||
@ -956,6 +971,10 @@ SheetCreateExisting: Folgende Pseudonyme haben bereits abgegeben:
|
||||
CorrGrade: Korrekturen eintragen
|
||||
|
||||
UserAccountDeleted name@Text: Konto für #{name} wurde gelöscht!
|
||||
UserSubmissionsDeleted n@Int: #{tshow n} Abgaben wurden unwiderruflich gelöscht.
|
||||
UserGroupSubmissionsKept n@Int: #{tshow n} Gruppenabgaben verbleiben in der Datenbank, aber die Zuordnung zum Benutzer wurde gelöscht. Gruppenabgaben können dadurch zu Einzelabgaben werden, die dann mit dem letzten Benutzer gelöscht werden.
|
||||
UserSubmissionGroupsDeleted count@Int64: #{tshow count} benannte Abgabengruppen wurden gelöscht, da sie ohne den Nutzer leer wären.
|
||||
UserAccountDeleteWarning: Achtung, dies löscht den kompletten Benutzer unwiderruflich und mit allen assoziierten Daten aus der Datenbank. Prüfungsdaten müssen jedoch langfristig gespeichert bleiben!
|
||||
|
||||
HelpTitle : Hilfe
|
||||
HelpAnswer: Antworten an
|
||||
@ -1028,7 +1047,6 @@ EncodedSecretBoxCouldNotDecodeNonce: Konnte secretbox-nonce nicht dekodieren
|
||||
EncodedSecretBoxCouldNotOpenSecretBox: Konnte libsodium-secretbox nicht öffnen (Verschlüsselte Daten sind nicht authentisch)
|
||||
EncodedSecretBoxCouldNotDecodePlaintext aesonErr@String: Konnte Klartext nicht JSON-dekodieren: #{aesonErr}
|
||||
ErrMsgHeading: Fehlermeldung entschlüsseln
|
||||
ErrorCryptoIdMismatch: Verschlüsselte Id der Abgabe passte nicht zu anderen Daten
|
||||
|
||||
InvalidRoute: Konnte URL nicht interpretieren
|
||||
|
||||
@ -1065,6 +1083,7 @@ MenuProfileData: Persönliche Daten
|
||||
MenuTermCreate: Neues Semester anlegen
|
||||
MenuCourseNew: Neuen Kurs anlegen
|
||||
MenuTermEdit: Semester editieren
|
||||
MenuTermCurrent: Aktuelles Semester
|
||||
MenuCorrection: Korrektur
|
||||
MenuCorrections: Korrekturen
|
||||
MenuCorrectionsOwn: Meine Korrekturen
|
||||
@ -1093,8 +1112,8 @@ MenuCorrectionsUpload: Korrekturen hochladen
|
||||
MenuCorrectionsDownload: Offene Abgaben herunterladen
|
||||
MenuCorrectionsCreate: Abgaben registrieren
|
||||
MenuCorrectionsGrade: Abgaben online korrigieren
|
||||
MenuCorrectionsAssign: Zuteilung Korrekturen
|
||||
MenuCorrectionsAssignSheet name@Text: Zuteilung Korrekturen von #{name}
|
||||
MenuCorrectionsAssign: Zuteilung der Korrekturen
|
||||
MenuCorrectionsAssignSheet name@Text: Zuteilung der Korrekturen von #{name}
|
||||
MenuAuthPreds: Authorisierungseinstellungen
|
||||
MenuTutorialDelete: Tutorium löschen
|
||||
MenuTutorialEdit: Tutorium editieren
|
||||
@ -1108,7 +1127,7 @@ MenuExamAddMembers: Prüfungsteilnehmer hinzufügen
|
||||
MenuExamOfficeExams: Prüfungen
|
||||
MenuExamOfficeFields: Fächer
|
||||
MenuExamOfficeUsers: Benutzer
|
||||
MenuLecturerInvite: Dozenten hinzufügen
|
||||
MenuLecturerInvite: Funktionäre hinzufügen
|
||||
MenuAllocationInfo: Hinweise zum Ablauf einer Zentralanmeldung
|
||||
MenuCourseApplicationsFiles: Dateien aller Bewerbungen
|
||||
MenuSchoolList: Institute
|
||||
@ -1118,6 +1137,58 @@ MenuCourseNewsEdit: Kursnachricht bearbeiten
|
||||
MenuCourseEventNew: Neuer Kurstermin
|
||||
MenuCourseEventEdit: Kurstermin bearbeiten
|
||||
|
||||
BreadcrumbSubmissionFile: Datei
|
||||
BreadcrumbSubmissionUserInvite: Einladung zur Abgabe
|
||||
BreadcrumbCryptoIDDispatch: CryptoID-Weiterleitung
|
||||
BreadcrumbCourseAppsFiles: Bewerbungsdateien
|
||||
BreadcrumbCourseNotes: Kursnotizen
|
||||
BreadcrumbHiWis: Korrektoren
|
||||
BreadcrumbMaterial: Material
|
||||
BreadcrumbSheet: Übungsblatt
|
||||
BreadcrumbTutorial: Tutorium
|
||||
BreadcrumbExam: Prüfung
|
||||
BreadcrumbApplicant: Bewerber
|
||||
BreadcrumbCourseRegister: Anmelden
|
||||
BreadcrumbCourseRegisterTemplate: Bewerbungsvorlagen
|
||||
BreadcrumbCourseFavourite: Favorisieren
|
||||
BreadcrumbCourse: Kurs
|
||||
BreadcrumbAllocationRegister: Teilnahme registrieren
|
||||
BreadcrumbAllocation: Zentralanmeldung
|
||||
BreadcrumbTerm: Semester
|
||||
BreadcrumbSchool: Institut
|
||||
BreadcrumbUser: Benutzer
|
||||
BreadcrumbStatic: Statische Resource
|
||||
BreadcrumbFavicon: Favicon
|
||||
BreadcrumbRobots: robots.txt
|
||||
BreadcrumbLecturerInvite: Einladung zum Kursverwalter
|
||||
BreadcrumbExamOfficeUserInvite: Einladung bzgl. Prüfungsleistungen
|
||||
BreadcrumbFunctionaryInvite: Einladung zum Instituts-Funktionär
|
||||
BreadcrumbUserDelete: Nutzer-Account löschen
|
||||
BreadcrumbUserHijack: Nutzer-Sitzung übernehmen
|
||||
BreadcrumbSystemMessage: Statusmeldung
|
||||
BreadcrumbSubmission: Abgabe
|
||||
BreadcrumbCourseNews: Kursnachricht
|
||||
BreadcrumbCourseNewsDelete: Kursnachricht löschen
|
||||
BreadcrumbCourseEventDelete: Kurstermin löschen
|
||||
BreadcrumbProfile: Einstellungen
|
||||
BreadcrumbAllocationInfo: Ablauf einer Zentralanmeldung
|
||||
BreadcrumbCourseParticipantInvitation: Einladung zum Kursteilnehmer
|
||||
BreadcrumbMaterialArchive: Archiv
|
||||
BreadcrumbMaterialFile: Datei
|
||||
BreadcrumbSheetArchive: Dateien
|
||||
BreadcrumbSheetIsCorrector: Korrektor-Überprüfung
|
||||
BreadcrumbSheetPseudonym: Pseudonym
|
||||
BreadcrumbSheetCorrectorInvite: Einladung zum Korrektor
|
||||
BreadcrumbSheetFile: Datei
|
||||
BreadcrumbTutorialRegister: Anmelden
|
||||
BreadcrumbTutorInvite: Einladung zum Tutor
|
||||
BreadcrumbExamCorrectorInvite: Einladung zum Prüfungskorrektor
|
||||
BreadcrumbExamParticipantInvite: Einladung zum Prüfungsteilnehmer
|
||||
BreadcrumbExamRegister: Anmelden
|
||||
BreadcrumbApplicationFiles: Bewerbungsdateien
|
||||
BreadcrumbCourseNewsArchive: Archiv
|
||||
BreadcrumbCourseNewsFile: Datei
|
||||
|
||||
AuthPredsInfo: Um eigene Veranstaltungen aus Sicht der Teilnehmer anzusehen, können Veranstalter und Korrektoren hier die Prüfung ihrer erweiterten Berechtigungen temporär deaktivieren. Abgewählte Prädikate schlagen immer fehl. Abgewählte Prädikate werden also nicht geprüft um Zugriffe zu gewähren, welche andernfalls nicht erlaubt wären. Diese Einstellungen gelten nur temporär bis Ihre Sitzung abgelaufen ist, d.h. bis ihr Browser-Cookie abgelaufen ist. Durch Abwahl von Prädikaten kann man sich höchstens temporär aussperren.
|
||||
AuthPredsActive: Aktive Authorisierungsprädikate
|
||||
AuthPredsActiveChanged: Authorisierungseinstellungen für aktuelle Sitzung gespeichert
|
||||
@ -1196,7 +1267,7 @@ RGTutorialParticipants: Tutorium-Teilnehmer
|
||||
|
||||
MultiSelectFieldTip: Mehrfach-Auswahl ist möglich (Umschalt bzw. Strg)
|
||||
MultiEmailFieldTip: Es sind mehrere, Komma-separierte, E-Mail-Addressen möglich
|
||||
EmailInvitationWarning: Dem System ist kein Nutzer mit dieser Addresse bekannt. Es wird eine Einladung per E-Mail versandt.
|
||||
EmailInvitationWarning: Es konnte kein Nutzer mit dieser Addresse im System gefunden werden (ggf. unter gewissen Einschränkungen). Es wird eine Einladung per E-Mail versandt.
|
||||
|
||||
LecturerInvitationAccepted lType@Text csh@CourseShorthand: Sie wurden als #{lType} für #{csh} eingetragen
|
||||
LecturerInvitationDeclined csh@CourseShorthand: Sie haben die Einladung, Kursverwalter für #{csh} zu werden, abgelehnt
|
||||
@ -1289,7 +1360,7 @@ TutorialTutorControlled: Tutoren dürfen Tutorium editieren
|
||||
TutorialTutorControlledTip: Sollen Tutoren beliebige Aspekte dieses Tutoriums (Name, Registrierungs-Gruppe, Raum, Zeit, andere Tutoren, ...) beliebig editieren dürfen?
|
||||
|
||||
CourseExams: Prüfungen
|
||||
CourseTutorials: Übungen
|
||||
CourseTutorials: Tutorien
|
||||
|
||||
ParticipantsN n@Int: #{n} Teilnehmer
|
||||
TutorialDeleteQuestion: Wollen Sie das unten aufgeführte Tutorium wirklich löschen?
|
||||
@ -1412,7 +1483,7 @@ ExamBonusRoundNonPositive: Vielfaches, auf das gerundet werden soll, muss positi
|
||||
ExamBonusRoundTip: Bonuspunkte werden kaufmännisch auf ein Vielfaches der angegeben Zahl gerundet.
|
||||
|
||||
ExamAutomaticOccurrenceAssignment: Automatische Termin- bzw. Raumzuteilung
|
||||
ExamAutomaticOccurrenceAssignmentTip: Sollen Prüfungsteilnehmer zum Zeitpunkt der Bekanntgabe der Raum- bzw. Terminzuteilung automatisch auf die zur Verfügung stehenden Räume bzw. Termine verteilt werden? Manuelle Umverteilung bzw. vorheriges Festlegen von Zuteilungen einzelner Teilnehmer ist trotzdem möglich.
|
||||
ExamAutomaticOccurrenceAssignmentTip: Sollen Prüfungsteilnehmer automatisch auf die zur Verfügung stehenden Räume bzw. Termine verteilt werden? Manuelle Umverteilung bzw. vorheriges Festlegen von Zuteilungen einzelner Teilnehmer ist trotzdem möglich.
|
||||
ExamOccurrenceRule: Verfahren
|
||||
ExamOccurrenceRuleParticipant: Termin- bzw. Raumzuteilungsverfahren
|
||||
ExamRoomManual': Keine automatische Zuteilung
|
||||
@ -1457,7 +1528,7 @@ ExamPartName: Titel
|
||||
ExamPartNameTip: Wird den Studierenden angezeigt
|
||||
ExamPartMaxPoints: Maximalpunktzahl
|
||||
ExamPartWeight: Gewichtung
|
||||
ExamPartWeightTip: Wird vor Anzeige oder Notenberechnung mit der erreichten Punktzahl und der Maximalpunktzahl multipliziert; Änderungen hier passen auch bestehende Korrekturergebnisse an
|
||||
ExamPartWeightTip: Wird vor Anzeige oder automatischen Notenberechnung mit der erreichten Punktzahl und der Maximalpunktzahl multipliziert; Änderungen hier passen also auch bestehende Korrekturergebnisse an (derart geänderte Noten müssen erneut manuell übernommen werden)
|
||||
ExamPartResultPoints: Erreichte Punkte
|
||||
|
||||
ExamNameTaken exam@ExamName: Es existiert bereits eine Prüfung mit Namen #{exam}
|
||||
@ -1527,6 +1598,8 @@ ExamUserMarkedSynchronised n@Int: #{n} #{pluralDE n "Prüfungsleistung" "Prüfun
|
||||
ExamOfficeExamUsersHeading: Prüfungsleistungen
|
||||
|
||||
CsvFile: CSV-Datei
|
||||
CsvImport: CSV-Import
|
||||
CsvExport: CSV-Export
|
||||
CsvModifyExisting: Existierende Einträge angleichen
|
||||
CsvAddNew: Neue Einträge einfügen
|
||||
CsvDeleteMissing: Fehlende Einträge entfernen
|
||||
@ -1681,6 +1754,7 @@ SchoolFunctionInvitationAccepted school@SchoolName renderedFunction@Text: #{rend
|
||||
AllocationActive: Aktiv
|
||||
AllocationName: Name
|
||||
AllocationAvailableCourses: Kurse
|
||||
AllocationApplication: Bewerbung
|
||||
AllocationAppliedCourses: Bewerbungen
|
||||
AllocationNumCoursesAvailableApplied available@Int applied@Int: Sie haben sich bisher für #{applied}/#{available} #{pluralDE applied "Kurs" "Kursen"} beworben
|
||||
AllocationTitle termText@Text ssh'@SchoolShorthand allocation@AllocationName: #{termText} - #{ssh'}: #{allocation}
|
||||
@ -1879,7 +1953,7 @@ AcceptApplicationsSecondaryRandom: Zufällig
|
||||
AcceptApplicationsSecondaryTime: Nach Zeitpunkt der Bewerbung
|
||||
|
||||
CsvOptions: CSV-Optionen
|
||||
CsvOptionsTip: Diese Einstellungen betreffen nur den CSV-Export; beim Import werden die verwendeten Einstellungen automatisch ermittelt. Als Zeichenkodierung wird beim Import stets Unicode erwartet.
|
||||
CsvOptionsTip: Diese Einstellungen betreffen primär den CSV-Export; beim Import werden die meisten Einstellungen automatisch ermittelt. Als Zeichenkodierung wird beim Import die selbe Kodierung wie beim Export erwartet.
|
||||
CsvFormatOptions: Dateiformat
|
||||
CsvTimestamp: Zeitstempel
|
||||
CsvTimestampTip: Soll an den Namen jeder exportierten CSV-Datei ein Zeitstempel vorne angehängt werden?
|
||||
@ -1898,6 +1972,7 @@ CsvDelimiterNull: Null-Byte
|
||||
CsvDelimiterTab: Tabulator
|
||||
CsvDelimiterComma: Komma
|
||||
CsvDelimiterColon: Doppelpunkt
|
||||
CsvDelimiterSemicolon: Strichpunkt
|
||||
CsvDelimiterBar: Senkrechter Strich
|
||||
CsvDelimiterSpace: Leerzeichen
|
||||
CsvDelimiterUnitSep: Teilgruppentrennzeichen
|
||||
@ -1987,3 +2062,61 @@ ShortSexNotApplicable: k.A.
|
||||
|
||||
ShowSex: Geschlechter anderer Nutzer anzeigen
|
||||
ShowSexTip: Sollen in Kursteilnehmer-Tabellen u.Ä. die Geschlechter der Nutzer angezeigt werden?
|
||||
|
||||
MenuLanguage: Sprache
|
||||
LanguageChanged: Sprache erfolgreich geändert
|
||||
|
||||
ProfileCorrector: Korrektor
|
||||
ProfileCourses: Eigene Kurse
|
||||
ProfileCourseParticipations: Kursanmeldungen
|
||||
ProfileCourseExamResults: Prüfungsleistungen
|
||||
ProfileTutorials: Eigene Tutorien
|
||||
ProfileTutorialParticipations: Tutorien
|
||||
ProfileSubmissionGroups: Abgabegruppen
|
||||
ProfileSubmissions: Abgaben
|
||||
ProfileRemark: Hinweis
|
||||
ProfileGroupSubmissionDates: Bei Gruppenabgaben wird kein Datum angezeigt, wenn Sie die Gruppenabgabe nie selbst hochgeladen haben.
|
||||
ProfileCorrectorRemark: Die oberhalb angezeigte Tabelle zeigt nur prinzipielle Einteilungen als Korrektor zu einem Übungsblatt. Auch ohne Einteilung können Korrekturen einzeln zugewiesen werden, welche hier dann nicht aufgeführt werden.
|
||||
ProfileCorrections: Auflistung aller zugewiesenen Korrekturen
|
||||
|
||||
GroupSizeNotNatural: „Gruppengröße“ muss eine natürliche Zahl sein
|
||||
AmbiguousEmail: E-Mail Adresse nicht eindeutig
|
||||
CourseDescriptionPlaceholder: Bitte mindestens die Modulbeschreibung angeben
|
||||
CourseHomepageExternalPlaceholder: Optionale externe URL
|
||||
PointsPlaceholder: Punktezahl
|
||||
RFC1766: RFC1766-Sprachcode
|
||||
|
||||
TermShort: Kürzel
|
||||
TermCourseCount: Kurse
|
||||
TermStart: Semesteranfang
|
||||
TermEnd: Semesterende
|
||||
TermStartMustMatchName: Jahreszahl im Namenskürzel stimmt nicht mit Semesterbeginn überein.
|
||||
TermEndMustBeAfterStart: Semester darf nicht enden, bevor es beginnt.
|
||||
TermLectureEndMustBeAfterStart: Vorlesungszeit muss vor ihrem Ende anfgangen.
|
||||
TermStartMustBeBeforeLectureStart: Semester muss vor der Vorlesungszeit beginnen.
|
||||
TermEndMustBeAfterLectureEnd: Vorlesungszeit muss vor dem Semester enden.
|
||||
AdminPageEmpty: Diese Seite soll eine Übersichtsseite für Administratoren werden. Aktuell finden sich hier nur Links zu wichtigen Administrator-Funktionalitäten.
|
||||
HaveCorrectorAccess sheetName@SheetName: Sie haben Korrektor-Zugang zu #{original sheetName}.
|
||||
FavouritesPlaceholder: Anzahl Favoriten
|
||||
FavouritesNotNatural: Anzahl der Favoriten muss eine natürliche Zahl sein!
|
||||
FavouritesSemestersPlaceholder: Anzahl Semester
|
||||
FavouritesSemestersNotNatural: Anzahl der Favoriten-Semester muss eine natürliche Zahl sein!
|
||||
|
||||
ProfileTitle: Benutzereinstellungen
|
||||
|
||||
GlossaryTitle: Begriffsverzeichnis
|
||||
MenuGlossary: Begriffsverzeichnis
|
||||
|
||||
Applicant: Bewerber
|
||||
CourseParticipant: Kursteilnehmer
|
||||
Administrator: Administrator
|
||||
CsvFormat: CSV-Format
|
||||
ExerciseSheet: Übungsblatt
|
||||
DefinitionCourseEvents: Kurstermine
|
||||
DefinitionCourseNews: Kurs-Aktuelles
|
||||
Invitations: Einladungen
|
||||
SheetSubmission: Abgabe
|
||||
CommCourse: Kursmitteilung
|
||||
CommTutorial: Tutorium-Mitteilung
|
||||
Clone: Klonen
|
||||
Deficit: Defizit
|
||||
2118
messages/uniworx/en-eu.msg
Normal file
2118
messages/uniworx/en-eu.msg
Normal file
File diff suppressed because it is too large
Load Diff
@ -28,7 +28,7 @@ User json -- Each Uni2work user has a corresponding row in this table; create
|
||||
dateFormat DateTimeFormat "default='%d.%m.%Y'" -- preferred Date-only display format for user; user-defined
|
||||
timeFormat DateTimeFormat "default='%R'" -- preferred Time-only display format for user; user-defined
|
||||
downloadFiles Bool default=false -- Should files be opened in browser or downloaded? (users often oblivious that their browser has a setting for this)
|
||||
mailLanguages MailLanguages "default='[]'::jsonb" -- Preferred language for eMail; i18n not yet implemented; user-defined
|
||||
languages Languages Maybe -- Preferred language; user-defined
|
||||
notificationSettings NotificationSettings -- Bit-array for which events email notifications are requested by user; user-defined
|
||||
warningDays NominalDiffTime default=1209600 -- timedistance to pending deadlines for homepage infos
|
||||
csvOptions CsvOptions "default='{}'::jsonb"
|
||||
|
||||
@ -140,6 +140,7 @@ dependencies:
|
||||
- retry
|
||||
- generic-lens
|
||||
- array
|
||||
- cookie
|
||||
|
||||
other-extensions:
|
||||
- GeneralizedNewtypeDeriving
|
||||
|
||||
2
routes
2
routes
@ -63,6 +63,7 @@
|
||||
/info/lecturer InfoLecturerR GET !lecturer
|
||||
/info/data DataProtR GET !free
|
||||
/info/allocation InfoAllocationR GET !free
|
||||
/info/glossary GlossaryR GET !free
|
||||
/impressum ImpressumR GET !free
|
||||
/version VersionR GET !free
|
||||
|
||||
@ -73,6 +74,7 @@
|
||||
/user/authpreds AuthPredsR GET POST !free
|
||||
/user/set-display-email SetDisplayEmailR GET POST !free
|
||||
/user/csv-options CsvOptionsR GET POST !free
|
||||
/user/lang LangR POST !free
|
||||
|
||||
/exam-office ExamOfficeR !exam-office:
|
||||
/ EOExamsR GET
|
||||
|
||||
@ -13,6 +13,7 @@ import qualified Data.CaseInsensitive as CI
|
||||
|
||||
|
||||
data DummyMessage = MsgDummyIdent
|
||||
| MsgDummyIdentPlaceholder
|
||||
| MsgDummyNoFormData
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
||||
|
||||
@ -24,7 +25,9 @@ dummyForm :: ( RenderMessage (HandlerSite m) FormMessage
|
||||
, Button (HandlerSite m) ButtonSubmit
|
||||
, MonadHandler m
|
||||
) => AForm m (CI Text)
|
||||
dummyForm = areq (ciField & addDatalist userList) (fslI MsgDummyIdent & noAutocomplete) Nothing
|
||||
dummyForm = wFormToAForm $ do
|
||||
mr <- getMessageRender
|
||||
aFormToWForm $ areq (ciField & addDatalist userList) (fslpI MsgDummyIdent (mr MsgDummyIdentPlaceholder) & noAutocomplete & addName PostLoginDummy) Nothing
|
||||
where
|
||||
userList = fmap mkOptionList . runDB $ withReaderT projectBackend (map toOption <$> selectList [] [Asc UserIdent] :: ReaderT SqlBackend _ [Option UserIdent])
|
||||
toOption (Entity _ User{..}) = Option userDisplayName userIdent (CI.original userIdent)
|
||||
|
||||
@ -36,6 +36,7 @@ data CampusLogin = CampusLogin
|
||||
data CampusMessage = MsgCampusIdentPlaceholder
|
||||
| MsgCampusIdent
|
||||
| MsgCampusPassword
|
||||
| MsgCampusPasswordPlaceholder
|
||||
| MsgCampusSubmit
|
||||
| MsgCampusInvalidCredentials
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
||||
@ -129,7 +130,7 @@ campusForm = do
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
|
||||
ident <- wreq ciField (fslpI MsgCampusIdent (mr MsgCampusIdentPlaceholder) & addAttr "autofocus" "") Nothing
|
||||
password <- wreq passwordField (fslI MsgCampusPassword) Nothing
|
||||
password <- wreq passwordField (fslpI MsgCampusPassword (mr MsgCampusPasswordPlaceholder)) Nothing
|
||||
|
||||
return $ CampusLogin
|
||||
<$> ident
|
||||
|
||||
@ -22,7 +22,9 @@ data HashLogin = HashLogin
|
||||
} deriving (Generic, Typeable)
|
||||
|
||||
data PWHashMessage = MsgPWHashIdent
|
||||
| MsgPWHashIdentPlaceholder
|
||||
| MsgPWHashPassword
|
||||
| MsgPWHashPasswordPlaceholder
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
||||
|
||||
|
||||
@ -30,9 +32,11 @@ hashForm :: ( RenderMessage (HandlerSite m) FormMessage
|
||||
, RenderMessage (HandlerSite m) PWHashMessage
|
||||
, MonadHandler m
|
||||
) => AForm m HashLogin
|
||||
hashForm = HashLogin
|
||||
<$> areq ciField (fslpI MsgPWHashIdent "Identifikation") Nothing
|
||||
<*> areq passwordField (fslpI MsgPWHashPassword "Passwort") Nothing
|
||||
hashForm = wFormToAForm $ do
|
||||
mr <- getMessageRender
|
||||
aFormToWForm $ HashLogin
|
||||
<$> areq ciField (fslpI MsgPWHashIdent (mr MsgPWHashIdentPlaceholder)) Nothing
|
||||
<*> areq passwordField (fslpI MsgPWHashPassword (mr MsgPWHashPasswordPlaceholder)) Nothing
|
||||
|
||||
|
||||
hashLogin :: forall site.
|
||||
|
||||
@ -43,7 +43,9 @@ import Data.Map (Map, (!?))
|
||||
import qualified Data.Map as Map
|
||||
import qualified Data.HashSet as HashSet
|
||||
|
||||
import Data.List (nubBy, (!!), findIndex)
|
||||
import Data.List (nubBy, (!!), findIndex, inits)
|
||||
|
||||
import Web.Cookie
|
||||
|
||||
import Data.Monoid (Any(..))
|
||||
|
||||
@ -72,6 +74,7 @@ import Utils.SystemMessage
|
||||
import Text.Shakespeare.Text (st)
|
||||
|
||||
import Yesod.Form.I18n.German
|
||||
import Yesod.Form.I18n.English
|
||||
import qualified Yesod.Auth.Message as Auth
|
||||
|
||||
import qualified Data.Conduit.List as C
|
||||
@ -240,6 +243,47 @@ noneMoreDE num noneText someText
|
||||
| num == 0 = noneText
|
||||
| otherwise = someText
|
||||
|
||||
pluralEN :: (Eq a, Num a)
|
||||
=> a -- ^ Count
|
||||
-> Text -- ^ Singular
|
||||
-> Text -- ^ Plural
|
||||
-> Text
|
||||
pluralEN num singularForm pluralForm
|
||||
| num == 1 = singularForm
|
||||
| otherwise = pluralForm
|
||||
|
||||
noneOneMoreEN :: (Eq a, Num a)
|
||||
=> a -- ^ Count
|
||||
-> Text -- ^ None
|
||||
-> Text -- ^ Singular
|
||||
-> Text -- ^ Plural
|
||||
-> Text
|
||||
noneOneMoreEN num noneText singularForm pluralForm
|
||||
| num == 0 = noneText
|
||||
| num == 1 = singularForm
|
||||
| otherwise = pluralForm
|
||||
|
||||
noneMoreEN :: (Eq a, Num a)
|
||||
=> a -- ^ Count
|
||||
-> Text -- ^ None
|
||||
-> Text -- ^ Some
|
||||
-> Text
|
||||
noneMoreEN num noneText someText
|
||||
| num == 0 = noneText
|
||||
| otherwise = someText
|
||||
|
||||
ordinalEN :: ToMessage a
|
||||
=> a
|
||||
-> Text
|
||||
ordinalEN (toMessage -> numStr) = case lastChar of
|
||||
Just '1' -> [st|#{numStr}st|]
|
||||
Just '2' -> [st|#{numStr}nd|]
|
||||
Just '3' -> [st|#{numStr}rd|]
|
||||
_other -> [st|#{numStr}th|]
|
||||
where
|
||||
lastChar = last <$> fromNullable numStr
|
||||
|
||||
|
||||
-- Convenience Type for Messages, since Yesod messages cannot deal with compound type identifiers
|
||||
type IntMaybe = Maybe Int
|
||||
type TextList = [Text]
|
||||
@ -250,17 +294,12 @@ maybeToMessage _ Nothing _ = mempty
|
||||
maybeToMessage before (Just x) after = before <> (toMessage x) <> after
|
||||
|
||||
-- Messages creates type UniWorXMessage and RenderMessage UniWorX instance
|
||||
mkMessage "UniWorX" "messages/uniworx" "de"
|
||||
mkMessage "UniWorX" "messages/uniworx" "de-de-formal"
|
||||
mkMessageVariant "UniWorX" "Campus" "messages/campus" "de"
|
||||
mkMessageVariant "UniWorX" "Dummy" "messages/dummy" "de"
|
||||
mkMessageVariant "UniWorX" "PWHash" "messages/pw-hash" "de"
|
||||
mkMessageVariant "UniWorX" "Button" "messages/button" "de"
|
||||
mkMessageVariant "UniWorX" "Frontend" "messages/frontend" "de"
|
||||
|
||||
-- This instance is required to use forms. You can modify renderMessage to
|
||||
-- achieve customized and internationalized form validation messages.
|
||||
instance RenderMessage UniWorX FormMessage where
|
||||
renderMessage _ _ = germanFormMessage -- TODO
|
||||
mkMessageVariant "UniWorX" "Frontend" "messages/frontend" "de-de-formal"
|
||||
|
||||
instance RenderMessage UniWorX TermIdentifier where
|
||||
renderMessage foundation ls TermIdentifier{..} = case season of
|
||||
@ -301,9 +340,11 @@ instance RenderMessage UniWorX Load where
|
||||
newtype MsgLanguage = MsgLanguage Lang
|
||||
deriving (Eq, Ord, Show, Read)
|
||||
instance RenderMessage UniWorX MsgLanguage where
|
||||
renderMessage foundation ls (MsgLanguage lang@(Text.splitOn "-" -> lang'))
|
||||
| ["de", "DE"] <- lang' = mr MsgGermanGermany
|
||||
| ("de" : _) <- lang' = mr MsgGerman
|
||||
renderMessage foundation ls (MsgLanguage lang@(map mk . Text.splitOn "-" -> lang'))
|
||||
| ("de" : "DE" : _) <- lang' = mr MsgGermanGermany
|
||||
| ("de" : _) <- lang' = mr MsgGerman
|
||||
| ("en" : "EU" : _) <- lang' = mr MsgEnglishEurope
|
||||
| ("en" : _) <- lang' = mr MsgEnglish
|
||||
| otherwise = lang
|
||||
where
|
||||
mr = renderMessage foundation ls
|
||||
@ -511,13 +552,13 @@ instance Button UniWorX ButtonSubmit where
|
||||
|
||||
|
||||
getTimeLocale' :: [Lang] -> TimeLocale
|
||||
getTimeLocale' = $(timeLocaleMap [("de", "de_DE.utf8")])
|
||||
getTimeLocale' = $(timeLocaleMap [("de-de", "de_DE.utf8"), ("en-GB", "en_GB.utf8")])
|
||||
|
||||
appTZ :: TZ
|
||||
appTZ = $(includeSystemTZ "Europe/Berlin")
|
||||
|
||||
appLanguages :: NonEmpty Lang
|
||||
appLanguages = "de-DE" :| []
|
||||
appLanguages = "de-de-formal" :| ["en-eu"]
|
||||
|
||||
appLanguagesOpts :: ( MonadHandler m
|
||||
, HandlerSite m ~ UniWorX
|
||||
@ -533,6 +574,13 @@ appLanguagesOpts = do
|
||||
langOptions = map mkOption $ toList appLanguages
|
||||
return $ mkOptionList langOptions
|
||||
|
||||
-- This instance is required to use forms. You can modify renderMessage to
|
||||
-- achieve customized and internationalized form validation messages.
|
||||
instance RenderMessage UniWorX FormMessage where
|
||||
renderMessage _ ls = case lang of
|
||||
("en" : _) -> englishFormMessage
|
||||
_other -> germanFormMessage
|
||||
where lang = Text.splitOn "-" $ selectLanguage' appLanguages ls
|
||||
|
||||
instance RenderMessage UniWorX WeekDay where
|
||||
renderMessage _ ls wDay = pack $ map fst (wDays $ getTimeLocale' ls) !! fromEnum wDay
|
||||
@ -1504,6 +1552,15 @@ redirectAccess url = do
|
||||
Authorized -> redirect url
|
||||
_ -> permissionDeniedI MsgUnauthorizedRedirect
|
||||
|
||||
redirectAccessWith :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => Status -> Route UniWorX -> m a
|
||||
redirectAccessWith status url = do
|
||||
-- must hide URL if not authorized
|
||||
access <- evalAccess url False
|
||||
case access of
|
||||
Authorized -> redirectWith status url
|
||||
_ -> permissionDeniedI MsgUnauthorizedRedirect
|
||||
|
||||
|
||||
-- | Verify that the currently logged in user is lecturer or corrector for at least one sheet for the given course
|
||||
evalAccessCorrector :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX)
|
||||
=> TermId -> SchoolId -> CourseShorthand -> m AuthResult
|
||||
@ -1564,7 +1621,7 @@ instance Yesod UniWorX where
|
||||
-- b) Validates that incoming write requests include that token in either a header or POST parameter.
|
||||
-- To add it, chain it together with the defaultMiddleware: yesodMiddleware = defaultYesodMiddleware . defaultCsrfMiddleware
|
||||
-- For details, see the CSRF documentation in the Yesod.Core.Handler module of the yesod-core package.
|
||||
yesodMiddleware = headerMessagesMiddleware . defaultYesodMiddleware . normalizeRouteMiddleware . defaultCsrfMiddleware . updateFavouritesMiddleware
|
||||
yesodMiddleware = languagesMiddleware appLanguages . headerMessagesMiddleware . defaultYesodMiddleware . normalizeRouteMiddleware . defaultCsrfMiddleware . updateFavouritesMiddleware
|
||||
where
|
||||
updateFavouritesMiddleware :: Handler a -> Handler a
|
||||
updateFavouritesMiddleware handler = (*> handler) . runMaybeT $ do
|
||||
@ -1683,6 +1740,17 @@ instance Yesod UniWorX where
|
||||
makeLogger = readTVarIO . snd . appLogger
|
||||
|
||||
|
||||
langForm :: Form (Lang, Route UniWorX)
|
||||
langForm csrf = do
|
||||
lang <- selectLanguage appLanguages
|
||||
route <- getCurrentRoute
|
||||
(urlRes, urlView) <- mreq hiddenField ("" & addName ("referer" :: Text)) route
|
||||
(langBoxRes, langBoxView) <- mreq
|
||||
(selectField appLanguagesOpts)
|
||||
("" & addAttr "multiple" "multiple" & addAttr "size" (tshow . min 10 $ length appLanguages) & addAutosubmit & addName ("lang" :: Text))
|
||||
(Just lang)
|
||||
return ((,) <$> langBoxRes <*> urlRes, toWidget csrf <> fvInput urlView <> fvInput langBoxView)
|
||||
|
||||
siteLayoutMsg :: (RenderMessage site msg, site ~ UniWorX) => msg -> Widget -> Handler Html
|
||||
siteLayoutMsg msg widget = do
|
||||
mr <- getMessageRender
|
||||
@ -1707,7 +1775,21 @@ siteLayout' headingOverride widget = do
|
||||
mcurrentRoute <- getCurrentRoute
|
||||
|
||||
-- Get the breadcrumbs, as defined in the YesodBreadcrumbs instance.
|
||||
(title, parents) <- breadcrumbs
|
||||
let
|
||||
breadcrumbs' mcRoute = do
|
||||
mr <- getMessageRender
|
||||
case mcRoute of
|
||||
Nothing -> return (mr MsgErrorResponseTitleNotFound, [])
|
||||
Just cRoute -> do
|
||||
(title, next) <- breadcrumb cRoute
|
||||
crumbs <- go [] next
|
||||
return (title, crumbs)
|
||||
where
|
||||
go crumbs Nothing = return crumbs
|
||||
go crumbs (Just cRoute) = do
|
||||
(title, next) <- breadcrumb cRoute
|
||||
go ((cRoute, title) : crumbs) next
|
||||
(title, parents) <- breadcrumbs' mcurrentRoute
|
||||
|
||||
-- let isParent :: Route UniWorX -> Bool
|
||||
-- isParent r = r == (fst parents)
|
||||
@ -1785,6 +1867,13 @@ siteLayout' headingOverride widget = do
|
||||
\authTag -> addMessageWidget Info $ msgModal [whamlet|_{MsgUnauthorizedDisabledTag authTag}|] (Left $ SomeRoute (AuthPredsR, catMaybes [(toPathPiece GetReferer, ) . toPathPiece <$> mcurrentRoute]))
|
||||
getMessages
|
||||
|
||||
(langFormView, langFormEnctype) <- generateFormPost $ identifyForm FIDLanguage langForm
|
||||
let langFormView' = wrapForm langFormView def
|
||||
{ formAction = Just $ SomeRoute LangR
|
||||
, formSubmit = FormAutoSubmit
|
||||
, formEncoding = langFormEnctype
|
||||
}
|
||||
|
||||
let highlight :: Route UniWorX -> Bool -- highlight last route in breadcrumbs, favorites taking priority
|
||||
highlight = let crumbs = mcons mcurrentRoute $ fst <$> reverse parents
|
||||
navItems = map (view _2) favourites ++ map (urlRoute . menuItemRoute . view _1) menuTypes
|
||||
@ -1841,6 +1930,7 @@ siteLayout' headingOverride widget = do
|
||||
let
|
||||
-- See Utils.Frontend.I18n and files in messages/frontend for message definitions
|
||||
frontendI18n = toJSON (mr :: FrontendMessage -> Text)
|
||||
frontendDatetimeLocale <- toJSON <$> selectLanguage frontendDatetimeLocales
|
||||
|
||||
pc <- widgetToPageContent $ do
|
||||
-- fonts
|
||||
@ -1883,136 +1973,261 @@ applySystemMessages = liftHandler . runDB . runConduit $ selectSource [] [] .| C
|
||||
Nothing -> addMessage systemMessageSeverity content
|
||||
|
||||
-- Define breadcrumbs.
|
||||
i18nCrumb :: ( RenderMessage (HandlerSite m) msg, MonadHandler m )
|
||||
=> msg
|
||||
-> Maybe (Route (HandlerSite m))
|
||||
-> m (Text, Maybe (Route (HandlerSite m)))
|
||||
i18nCrumb msg mbR = do
|
||||
mr <- getMessageRender
|
||||
return (mr msg, mbR)
|
||||
|
||||
-- `breadcrumb` _really_ needs to be total for _all_ routes
|
||||
--
|
||||
-- Even if routes are POST only or don't usually use `siteLayout` they will if
|
||||
-- an error occurs.
|
||||
--
|
||||
-- Keep in mind that Breadcrumbs are also shown by the 403-Handler,
|
||||
-- i.e. information might be leaked by not performing permission checks if the
|
||||
-- breadcrumb value depends on sensitive content (like an user's name).
|
||||
instance YesodBreadcrumbs UniWorX where
|
||||
breadcrumb (AuthR _) = return ("Login" , Just HomeR)
|
||||
breadcrumb HomeR = return ("Uni2work" , Nothing)
|
||||
breadcrumb UsersR = return ("Benutzer" , Just AdminR)
|
||||
breadcrumb AdminUserAddR = return ("Benutzer anlegen", Just UsersR)
|
||||
breadcrumb (AdminUserR _) = return ("Users" , Just UsersR)
|
||||
breadcrumb AdminR = return ("Administration", Nothing)
|
||||
breadcrumb AdminFeaturesR = return ("Test" , Just AdminR)
|
||||
breadcrumb AdminTestR = return ("Test" , Just AdminR)
|
||||
breadcrumb AdminErrMsgR = return ("Test" , Just AdminR)
|
||||
|
||||
breadcrumb SchoolListR = return ("Institute" , Just AdminR)
|
||||
breadcrumb (SchoolR ssh SchoolEditR) = return (original (unSchoolKey ssh), Just SchoolListR)
|
||||
breadcrumb SchoolNewR = return ("Neu" , Just SchoolListR)
|
||||
breadcrumb (AuthR _) = i18nCrumb MsgMenuLogin $ Just HomeR
|
||||
breadcrumb (StaticR _) = i18nCrumb MsgBreadcrumbStatic Nothing
|
||||
breadcrumb FaviconR = i18nCrumb MsgBreadcrumbFavicon Nothing
|
||||
breadcrumb RobotsR = i18nCrumb MsgBreadcrumbRobots Nothing
|
||||
|
||||
breadcrumb (ExamOfficeR EOExamsR) = return ("Prüfungen", Nothing)
|
||||
breadcrumb (ExamOfficeR EOFieldsR) = return ("Fächer" , Just $ ExamOfficeR EOExamsR)
|
||||
breadcrumb (ExamOfficeR EOUsersR) = return ("Benutzer" , Just $ ExamOfficeR EOExamsR)
|
||||
|
||||
breadcrumb InfoR = return ("Information" , Nothing)
|
||||
breadcrumb InfoLecturerR = return ("Veranstalter" , Just InfoR)
|
||||
breadcrumb DataProtR = return ("Datenschutz" , Just InfoR)
|
||||
breadcrumb InfoAllocationR = return ("Zentralanmeldungen", Just InfoR)
|
||||
breadcrumb ImpressumR = return ("Impressum" , Just InfoR)
|
||||
breadcrumb VersionR = return ("Versionsgeschichte", Just InfoR)
|
||||
|
||||
|
||||
breadcrumb HelpR = return ("Hilfe" , Just HomeR)
|
||||
|
||||
|
||||
breadcrumb HealthR = return ("Status" , Nothing)
|
||||
breadcrumb InstanceR = return ("Identifikation", Nothing)
|
||||
|
||||
|
||||
breadcrumb ProfileR = return ("Einstellungen" , Just HomeR)
|
||||
breadcrumb SetDisplayEmailR = return ("Öffentliche E-Mail Adresse", Just ProfileR)
|
||||
breadcrumb ProfileDataR = return ("Persönliche Daten", Just ProfileR)
|
||||
breadcrumb AuthPredsR = return ("Authorisierung" , Just ProfileR)
|
||||
|
||||
breadcrumb TermShowR = return ("Semester" , Just HomeR)
|
||||
breadcrumb TermCurrentR = return ("Aktuell" , Just TermShowR)
|
||||
breadcrumb TermEditR = return ("Neu" , Just TermCurrentR)
|
||||
breadcrumb (TermEditExistR tid) = return ("Editieren" , Just $ TermCourseListR tid)
|
||||
breadcrumb (TermCourseListR (unTermKey -> tid)) = getMessageRender <&> \mr -> (mr $ ShortTermIdentifier tid, Just CourseListR)
|
||||
|
||||
breadcrumb (TermSchoolCourseListR tid ssh) = return (original $ unSchoolKey ssh, Just $ TermCourseListR tid)
|
||||
|
||||
breadcrumb AllocationListR = return ("Zentralanmeldungen", Just HomeR)
|
||||
breadcrumb (AllocationR tid ssh ash AShowR) = do
|
||||
mr <- getMessageRender
|
||||
Entity _ Allocation{allocationName} <- runDB . getBy404 $ TermSchoolAllocationShort tid ssh ash
|
||||
return ([st|#{allocationName} (#{mr (ShortTermIdentifier (unTermKey tid))}, #{original (unSchoolKey ssh)})|], Just $ AllocationListR)
|
||||
|
||||
breadcrumb CourseListR = return ("Kurse" , Nothing)
|
||||
breadcrumb CourseNewR = return ("Neu" , Just CourseListR)
|
||||
breadcrumb (CourseR tid ssh csh CShowR) = return (original csh, Just $ TermSchoolCourseListR tid ssh)
|
||||
-- (CourseR tid ssh csh CRegisterR) -- is POST only
|
||||
breadcrumb (CourseR tid ssh csh CEditR) = return ("Editieren" , Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CourseR tid ssh csh CUsersR) = return ("Anmeldungen", Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CourseR tid ssh csh CAddUserR) = return ("Kursteilnehmer hinzufügen", Just $ CourseR tid ssh csh CUsersR)
|
||||
breadcrumb (CourseR tid ssh csh CInviteR) = return ("Einladung", Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CourseR tid ssh csh CExamOfficeR) = return ("Prüfungsamter", Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CourseR tid ssh csh (CUserR cID)) = do
|
||||
breadcrumb HomeR = i18nCrumb MsgMenuHome Nothing
|
||||
breadcrumb UsersR = i18nCrumb MsgMenuUsers $ Just AdminR
|
||||
breadcrumb AdminUserAddR = i18nCrumb MsgMenuUserAdd $ Just UsersR
|
||||
breadcrumb (AdminUserR cID) = maybeT (i18nCrumb MsgBreadcrumbUser $ Just UsersR) $ do
|
||||
guardM . hasReadAccessTo $ AdminUserR cID
|
||||
uid <- decrypt cID
|
||||
User{userDisplayName} <- runDB $ get404 uid
|
||||
return (userDisplayName, Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CourseR tid ssh csh CCorrectionsR) = return ("Abgaben" , Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CourseR tid ssh csh CAssignR) = return ("Zuteilung Korrekturen" , Just $ CourseR tid ssh csh CCorrectionsR)
|
||||
breadcrumb (CourseR tid ssh csh SheetListR) = return ("Übungen" , Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CourseR tid ssh csh SheetNewR ) = return ("Neu", Just $ CourseR tid ssh csh SheetListR)
|
||||
breadcrumb (CourseR tid ssh csh SheetCurrentR) = return ("Aktuelles Blatt", Just $ CourseR tid ssh csh SheetListR)
|
||||
breadcrumb (CourseR tid ssh csh SheetOldUnassignedR) = return ("Offene Abgaben", Just $ CourseR tid ssh csh SheetListR)
|
||||
breadcrumb (CourseR tid ssh csh CCommR ) = return ("Kursmitteilung", Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CourseR tid ssh csh CTutorialListR) = return ("Tutorien", Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CourseR tid ssh csh CTutorialNewR) = return ("Anlegen", Just $ CourseR tid ssh csh CTutorialListR)
|
||||
User{..} <- MaybeT . runDB $ get uid
|
||||
return (userDisplayName, Just UsersR)
|
||||
breadcrumb (AdminUserDeleteR cID) = i18nCrumb MsgBreadcrumbUserDelete . Just $ AdminUserR cID
|
||||
breadcrumb (AdminHijackUserR cID) = i18nCrumb MsgBreadcrumbUserHijack . Just $ AdminUserR cID
|
||||
breadcrumb (UserNotificationR cID) = do
|
||||
mayList <- hasReadAccessTo UsersR
|
||||
if
|
||||
| mayList
|
||||
-> i18nCrumb MsgMenuUserNotifications . Just $ AdminUserR cID
|
||||
| otherwise
|
||||
-> i18nCrumb MsgMenuUserNotifications $ Just ProfileR
|
||||
breadcrumb (UserPasswordR cID) = do
|
||||
mayList <- hasReadAccessTo UsersR
|
||||
if
|
||||
| mayList
|
||||
-> i18nCrumb MsgMenuUserPassword . Just $ AdminUserR cID
|
||||
| otherwise
|
||||
-> i18nCrumb MsgMenuUserPassword $ Just ProfileR
|
||||
breadcrumb AdminNewFunctionaryInviteR = i18nCrumb MsgMenuLecturerInvite $ Just UsersR
|
||||
breadcrumb AdminFunctionaryInviteR = i18nCrumb MsgBreadcrumbFunctionaryInvite Nothing
|
||||
|
||||
breadcrumb (CourseR tid ssh csh CNewsNewR) = return ("Neue Nachricht", Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CNewsR tid ssh csh _ CNShowR) = return ("Kursnachricht" , Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CNewsR tid ssh csh cID CNEditR) = return ("Bearbeiten" , Just $ CNewsR tid ssh csh cID CNShowR)
|
||||
breadcrumb (CNewsR tid ssh csh cID CNDeleteR) = return ("Löschen" , Just $ CNewsR tid ssh csh cID CNShowR)
|
||||
breadcrumb AdminR = i18nCrumb MsgAdminHeading Nothing
|
||||
breadcrumb AdminFeaturesR = i18nCrumb MsgAdminFeaturesHeading $ Just AdminR
|
||||
breadcrumb AdminTestR = i18nCrumb MsgMenuAdminTest $ Just AdminR
|
||||
breadcrumb AdminErrMsgR = i18nCrumb MsgMenuAdminErrMsg $ Just AdminR
|
||||
|
||||
breadcrumb SchoolListR = i18nCrumb MsgMenuSchoolList $ Just AdminR
|
||||
breadcrumb (SchoolR ssh SchoolEditR) = maybeT (i18nCrumb MsgBreadcrumbSchool $ Just SchoolListR) $ do
|
||||
School{..} <- MaybeT . runDB $ get ssh
|
||||
return (original schoolName, Just SchoolListR)
|
||||
breadcrumb SchoolNewR = i18nCrumb MsgMenuSchoolNew $ Just SchoolListR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh CExamListR) = return ("Prüfungen", Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CourseR tid ssh csh CExamNewR) = return ("Anlegen", Just $ CourseR tid ssh csh CExamListR)
|
||||
breadcrumb (ExamOfficeR EOExamsR) = i18nCrumb MsgMenuExamOfficeExams Nothing
|
||||
breadcrumb (ExamOfficeR EOFieldsR) = i18nCrumb MsgMenuExamOfficeFields . Just $ ExamOfficeR EOExamsR
|
||||
breadcrumb (ExamOfficeR EOUsersR) = i18nCrumb MsgMenuExamOfficeUsers . Just $ ExamOfficeR EOExamsR
|
||||
breadcrumb (ExamOfficeR EOUsersInviteR) = i18nCrumb MsgBreadcrumbExamOfficeUserInvite Nothing
|
||||
|
||||
breadcrumb (CourseR tid ssh csh CApplicationsR) = return ("Bewerbungen", Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb InfoR = i18nCrumb MsgMenuInformation Nothing
|
||||
breadcrumb InfoLecturerR = i18nCrumb MsgInfoLecturerTitle $ Just InfoR
|
||||
breadcrumb DataProtR = i18nCrumb MsgMenuDataProt $ Just InfoR
|
||||
breadcrumb InfoAllocationR = i18nCrumb MsgBreadcrumbAllocationInfo $ Just InfoR
|
||||
breadcrumb ImpressumR = i18nCrumb MsgMenuImpressum $ Just InfoR
|
||||
breadcrumb VersionR = i18nCrumb MsgMenuVersion $ Just InfoR
|
||||
|
||||
breadcrumb (CApplicationR tid ssh csh _ CAEditR) = return ("Bewerbung", Just $ CourseR tid ssh csh CApplicationsR)
|
||||
|
||||
breadcrumb (CExamR tid ssh csh examn EShowR) = return (original examn, Just $ CourseR tid ssh csh CExamListR)
|
||||
breadcrumb (CExamR tid ssh csh examn EEditR) = return ("Bearbeiten", Just $ CExamR tid ssh csh examn EShowR)
|
||||
breadcrumb (CExamR tid ssh csh examn EUsersR) = return ("Teilnehmer", Just $ CExamR tid ssh csh examn EShowR)
|
||||
breadcrumb (CExamR tid ssh csh examn EAddUserR) = return ("Prüfungsteilnehmer hinzufügen", Just $ CExamR tid ssh csh examn EUsersR)
|
||||
breadcrumb (CExamR tid ssh csh examn EGradesR) = return ("Prüfungsleistungen", Just $ CExamR tid ssh csh examn EShowR)
|
||||
breadcrumb HelpR = i18nCrumb MsgMenuHelp Nothing
|
||||
|
||||
breadcrumb (CTutorialR tid ssh csh tutn TUsersR) = return (original tutn, Just $ CourseR tid ssh csh CTutorialListR)
|
||||
breadcrumb (CTutorialR tid ssh csh tutn TEditR) = return ("Bearbeiten", Just $ CTutorialR tid ssh csh tutn TUsersR)
|
||||
breadcrumb (CTutorialR tid ssh csh tutn TDeleteR) = return ("Löschen", Just $ CTutorialR tid ssh csh tutn TUsersR)
|
||||
breadcrumb (CTutorialR tid ssh csh tutn TCommR) = return ("Mitteilung", Just $ CTutorialR tid ssh csh tutn TUsersR)
|
||||
|
||||
breadcrumb (CSheetR tid ssh csh shn SShowR) = return (original shn, Just $ CourseR tid ssh csh SheetListR)
|
||||
breadcrumb (CSheetR tid ssh csh shn SEditR) = return ("Bearbeiten" , Just $ CSheetR tid ssh csh shn SShowR)
|
||||
breadcrumb (CSheetR tid ssh csh shn SDelR ) = return ("Löschen" , Just $ CSheetR tid ssh csh shn SShowR)
|
||||
breadcrumb (CSheetR tid ssh csh shn SSubsR) = return ("Abgaben" , Just $ CSheetR tid ssh csh shn SShowR)
|
||||
breadcrumb (CSheetR tid ssh csh shn SAssignR) = return ("Zuteilung Korrekturen" , Just $ CSheetR tid ssh csh shn SSubsR)
|
||||
breadcrumb (CSheetR tid ssh csh shn SubmissionNewR) = return ("Abgabe", Just $ CSheetR tid ssh csh shn SShowR)
|
||||
breadcrumb (CSheetR tid ssh csh shn SubmissionOwnR) = return ("Abgabe", Just $ CSheetR tid ssh csh shn SShowR)
|
||||
breadcrumb (CSubmissionR tid ssh csh shn _ SubShowR) = return ("Abgabe", Just $ CSheetR tid ssh csh shn SShowR)
|
||||
-- (CSubmissionR tid ssh csh shn _ SubArchiveR) -- just for Download
|
||||
breadcrumb (CSubmissionR tid ssh csh shn cid CorrectionR) = return ("Korrektur", Just $ CSubmissionR tid ssh csh shn cid SubShowR)
|
||||
-- (CSubmissionR tid ssh csh shn _ SubDownloadR) -- just for Download
|
||||
breadcrumb (CSheetR tid ssh csh shn SCorrR) = return ("Korrektoren", Just $ CSheetR tid ssh csh shn SShowR)
|
||||
-- (CSheetR tid ssh csh shn SFileR) -- just for Downloads
|
||||
breadcrumb HealthR = i18nCrumb MsgMenuHealth Nothing
|
||||
breadcrumb InstanceR = i18nCrumb MsgMenuInstance Nothing
|
||||
|
||||
breadcrumb (CourseR tid ssh csh MaterialListR) = return ("Material" , Just $ CourseR tid ssh csh CShowR)
|
||||
breadcrumb (CourseR tid ssh csh MaterialNewR ) = return ("Neu" , Just $ CourseR tid ssh csh MaterialListR)
|
||||
breadcrumb (CMaterialR tid ssh csh mnm MShowR) = return (original mnm, Just $ CourseR tid ssh csh MaterialListR)
|
||||
breadcrumb (CMaterialR tid ssh csh mnm MEditR) = return ("Bearbeiten" , Just $ CMaterialR tid ssh csh mnm MShowR)
|
||||
breadcrumb (CMaterialR tid ssh csh mnm MDelR) = return ("Löschen" , Just $ CMaterialR tid ssh csh mnm MShowR)
|
||||
-- (CMaterialR tid ssh csh mnm MFileR) -- just for Downloads
|
||||
breadcrumb ProfileR = i18nCrumb MsgBreadcrumbProfile Nothing
|
||||
breadcrumb SetDisplayEmailR = i18nCrumb MsgUserDisplayEmail $ Just ProfileR
|
||||
breadcrumb ProfileDataR = i18nCrumb MsgMenuProfileData $ Just ProfileR
|
||||
breadcrumb AuthPredsR = i18nCrumb MsgMenuAuthPreds $ Just ProfileR
|
||||
breadcrumb CsvOptionsR = i18nCrumb MsgCsvOptions $ Just ProfileR
|
||||
breadcrumb LangR = i18nCrumb MsgMenuLanguage $ Just ProfileR
|
||||
|
||||
-- Others
|
||||
breadcrumb (CorrectionsR) = return ("Korrekturen", Just HomeR)
|
||||
breadcrumb (CorrectionsUploadR) = return ("Hochladen", Just CorrectionsR)
|
||||
breadcrumb TermShowR = i18nCrumb MsgMenuTermShow $ Just HomeR
|
||||
breadcrumb TermCurrentR = i18nCrumb MsgMenuTermCurrent $ Just TermShowR
|
||||
breadcrumb TermEditR = i18nCrumb MsgMenuTermCreate $ Just TermShowR
|
||||
breadcrumb (TermEditExistR tid) = i18nCrumb MsgMenuTermEdit . Just $ TermCourseListR tid
|
||||
breadcrumb (TermCourseListR tid) = maybeT (i18nCrumb MsgBreadcrumbTerm $ Just CourseListR) $ do -- redirect only, used in other breadcrumbs
|
||||
guardM . lift . runDB $ isJust <$> get tid
|
||||
i18nCrumb (ShortTermIdentifier $ unTermKey tid) $ Just CourseListR
|
||||
|
||||
breadcrumb (TermSchoolCourseListR tid ssh) = maybeT (i18nCrumb MsgBreadcrumbSchool . Just $ TermCourseListR tid) $ do -- redirect only, used in other breadcrumbs
|
||||
guardM . lift . runDB $
|
||||
(&&) <$> fmap isJust (get ssh)
|
||||
<*> fmap isJust (get tid)
|
||||
return (original $ unSchoolKey ssh, Just $ TermCourseListR tid)
|
||||
|
||||
breadcrumb AllocationListR = i18nCrumb MsgAllocationListTitle $ Just HomeR
|
||||
breadcrumb (AllocationR tid ssh ash AShowR) = maybeT (i18nCrumb MsgBreadcrumbAllocation $ Just AllocationListR) $ do
|
||||
mr <- getMessageRender
|
||||
Entity _ Allocation{allocationName} <- MaybeT . runDB . getBy $ TermSchoolAllocationShort tid ssh ash
|
||||
return ([st|#{allocationName} (#{mr (ShortTermIdentifier (unTermKey tid))}, #{original (unSchoolKey ssh)})|], Just $ AllocationListR)
|
||||
breadcrumb (AllocationR tid ssh ash ARegisterR) = i18nCrumb MsgBreadcrumbAllocationRegister . Just $ AllocationR tid ssh ash AShowR
|
||||
breadcrumb (AllocationR tid ssh ash (AApplyR cID)) = maybeT (i18nCrumb MsgBreadcrumbCourse . Just $ AllocationR tid ssh ash AShowR) $ do
|
||||
cid <- decrypt cID
|
||||
Course{..} <- hoist runDB $ do
|
||||
aid <- MaybeT . getKeyBy $ TermSchoolAllocationShort tid ssh ash
|
||||
guardM . lift $ exists [ AllocationCourseAllocation ==. aid, AllocationCourseCourse ==. cid ]
|
||||
MaybeT $ get cid
|
||||
return (original courseName, Just $ AllocationR tid ssh ash AShowR)
|
||||
|
||||
breadcrumb CourseListR = i18nCrumb MsgMenuCourseList Nothing
|
||||
breadcrumb CourseNewR = i18nCrumb MsgMenuCourseNew $ Just CourseListR
|
||||
breadcrumb (CourseR tid ssh csh CShowR) = maybeT (i18nCrumb MsgBreadcrumbCourse . Just $ TermSchoolCourseListR tid ssh) $ do
|
||||
guardM . lift . runDB . existsBy $ TermSchoolCourseShort tid ssh csh
|
||||
return (original csh, Just $ TermSchoolCourseListR tid ssh)
|
||||
breadcrumb (CourseR tid ssh csh CEditR) = i18nCrumb MsgMenuCourseEdit . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CUsersR) = i18nCrumb MsgMenuCourseMembers . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CAddUserR) = i18nCrumb MsgMenuCourseAddMembers . Just $ CourseR tid ssh csh CUsersR
|
||||
breadcrumb (CourseR tid ssh csh CInviteR) = i18nCrumb MsgBreadcrumbCourseParticipantInvitation . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CExamOfficeR) = i18nCrumb MsgMenuCourseExamOffice . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh (CUserR cID)) = maybeT (i18nCrumb MsgBreadcrumbUser . Just $ CourseR tid ssh csh CUsersR) $ do
|
||||
guardM . hasReadAccessTo . CourseR tid ssh csh $ CUserR cID
|
||||
uid <- decrypt cID
|
||||
User{userDisplayName} <- MaybeT . runDB $ get uid
|
||||
return (userDisplayName, Just $ CourseR tid ssh csh CUsersR)
|
||||
breadcrumb (CourseR tid ssh csh CCorrectionsR) = i18nCrumb MsgMenuSubmissions . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CAssignR) = i18nCrumb MsgMenuCorrectionsAssign . Just $ CourseR tid ssh csh CCorrectionsR
|
||||
breadcrumb (CourseR tid ssh csh SheetListR) = i18nCrumb MsgMenuSheetList . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh SheetNewR ) = i18nCrumb MsgMenuSheetNew . Just $ CourseR tid ssh csh SheetListR
|
||||
breadcrumb (CourseR tid ssh csh SheetCurrentR) = i18nCrumb MsgMenuSheetCurrent . Just $ CourseR tid ssh csh SheetListR
|
||||
breadcrumb (CourseR tid ssh csh SheetOldUnassignedR) = i18nCrumb MsgMenuSheetOldUnassigned . Just $ CourseR tid ssh csh SheetListR
|
||||
breadcrumb (CourseR tid ssh csh CCommR ) = i18nCrumb MsgMenuCourseCommunication . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CTutorialListR) = i18nCrumb MsgMenuTutorialList . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CTutorialNewR) = i18nCrumb MsgMenuTutorialNew . Just $ CourseR tid ssh csh CTutorialListR
|
||||
breadcrumb (CourseR tid ssh csh CFavouriteR) = i18nCrumb MsgBreadcrumbCourseFavourite . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CRegisterR) = i18nCrumb MsgBreadcrumbCourseRegister . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CRegisterTemplateR) = i18nCrumb MsgBreadcrumbCourseRegisterTemplate . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CLecInviteR) = i18nCrumb MsgBreadcrumbLecturerInvite . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CDeleteR) = i18nCrumb MsgMenuCourseDelete . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CHiWisR) = i18nCrumb MsgBreadcrumbHiWis . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CNotesR) = i18nCrumb MsgBreadcrumbCourseNotes . Just $ CourseR tid ssh csh CShowR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh CNewsNewR) = i18nCrumb MsgMenuCourseNewsNew . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh (CourseNewsR cID sRoute)) = case sRoute of
|
||||
CNShowR -> i18nCrumb MsgBreadcrumbCourseNews . Just $ CourseR tid ssh csh CShowR
|
||||
CNEditR -> i18nCrumb MsgMenuCourseNewsEdit . Just $ CNewsR tid ssh csh cID CNShowR
|
||||
CNDeleteR -> i18nCrumb MsgBreadcrumbCourseNewsDelete . Just $ CNewsR tid ssh csh cID CNShowR
|
||||
CNArchiveR -> i18nCrumb MsgBreadcrumbCourseNewsArchive . Just $ CNewsR tid ssh csh cID CNShowR
|
||||
CNFileR _ -> i18nCrumb MsgBreadcrumbCourseNewsFile . Just $ CNewsR tid ssh csh cID CNShowR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh CEventsNewR) = i18nCrumb MsgMenuCourseEventNew . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh (CourseEventR _cID sRoute)) = case sRoute of
|
||||
CEvEditR -> i18nCrumb MsgMenuCourseEventEdit . Just $ CourseR tid ssh csh CShowR
|
||||
CEvDeleteR -> i18nCrumb MsgBreadcrumbCourseEventDelete . Just $ CourseR tid ssh csh CShowR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh CExamListR) = i18nCrumb MsgMenuExamList . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CExamNewR) = i18nCrumb MsgMenuExamNew . Just $ CourseR tid ssh csh CExamListR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh CApplicationsR) = i18nCrumb MsgMenuCourseApplications . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh CAppsFilesR) = i18nCrumb MsgBreadcrumbCourseAppsFiles . Just $ CourseR tid ssh csh CApplicationsR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh (CourseApplicationR cID sRoute)) = case sRoute of
|
||||
CAEditR -> maybeT (i18nCrumb MsgBreadcrumbApplicant . Just $ CourseR tid ssh csh CApplicationsR) $ do
|
||||
guardM . hasReadAccessTo $ CApplicationR tid ssh csh cID CAEditR
|
||||
appId <- decrypt cID
|
||||
User{..} <- hoist runDB $ MaybeT (get appId) >>= MaybeT . get . courseApplicationUser
|
||||
return (userDisplayName, Just $ CourseR tid ssh csh CApplicationsR)
|
||||
CAFilesR -> i18nCrumb MsgBreadcrumbApplicationFiles . Just $ CApplicationR tid ssh csh cID CAEditR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh (ExamR examn sRoute)) = case sRoute of
|
||||
EShowR -> maybeT (i18nCrumb MsgBreadcrumbExam . Just $ CourseR tid ssh csh CExamListR) $ do
|
||||
guardM . hasReadAccessTo $ CExamR tid ssh csh examn EShowR
|
||||
return (original examn, Just $ CourseR tid ssh csh CExamListR)
|
||||
EEditR -> i18nCrumb MsgMenuExamEdit . Just $ CExamR tid ssh csh examn EShowR
|
||||
EUsersR -> i18nCrumb MsgMenuExamUsers . Just $ CExamR tid ssh csh examn EShowR
|
||||
EAddUserR -> i18nCrumb MsgMenuExamAddMembers . Just $ CExamR tid ssh csh examn EUsersR
|
||||
EGradesR -> i18nCrumb MsgMenuExamGrades . Just $ CExamR tid ssh csh examn EShowR
|
||||
ECInviteR -> i18nCrumb MsgBreadcrumbExamCorrectorInvite . Just $ CExamR tid ssh csh examn EShowR
|
||||
EInviteR -> i18nCrumb MsgBreadcrumbExamParticipantInvite . Just $ CExamR tid ssh csh examn EShowR
|
||||
ERegisterR -> i18nCrumb MsgBreadcrumbExamRegister . Just $ CExamR tid ssh csh examn EShowR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh (TutorialR tutn sRoute)) = case sRoute of
|
||||
TUsersR -> maybeT (i18nCrumb MsgBreadcrumbTutorial . Just $ CourseR tid ssh csh CTutorialListR) $ do
|
||||
guardM . hasReadAccessTo $ CTutorialR tid ssh csh tutn TUsersR
|
||||
return (original tutn, Just $ CourseR tid ssh csh CTutorialListR)
|
||||
TEditR -> i18nCrumb MsgMenuTutorialEdit . Just $ CTutorialR tid ssh csh tutn TUsersR
|
||||
TDeleteR -> i18nCrumb MsgMenuTutorialDelete . Just $ CTutorialR tid ssh csh tutn TUsersR
|
||||
TCommR -> i18nCrumb MsgMenuTutorialComm . Just $ CTutorialR tid ssh csh tutn TUsersR
|
||||
TRegisterR -> i18nCrumb MsgBreadcrumbTutorialRegister . Just $ CourseR tid ssh csh CShowR
|
||||
TInviteR -> i18nCrumb MsgBreadcrumbTutorInvite . Just $ CTutorialR tid ssh csh tutn TUsersR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh (SheetR shn sRoute)) = case sRoute of
|
||||
SShowR -> maybeT (i18nCrumb MsgBreadcrumbSheet . Just $ CourseR tid ssh csh SheetListR) $ do
|
||||
guardM . hasReadAccessTo $ CSheetR tid ssh csh shn SShowR
|
||||
return (original shn, Just $ CourseR tid ssh csh SheetListR)
|
||||
SEditR -> i18nCrumb MsgMenuSheetEdit . Just $ CSheetR tid ssh csh shn SShowR
|
||||
SDelR -> i18nCrumb MsgMenuSheetDelete . Just $ CSheetR tid ssh csh shn SShowR
|
||||
SSubsR -> i18nCrumb MsgMenuSubmissions . Just $ CSheetR tid ssh csh shn SShowR
|
||||
SAssignR -> i18nCrumb MsgMenuCorrectionsAssign . Just $ CSheetR tid ssh csh shn SSubsR
|
||||
SubmissionNewR -> i18nCrumb MsgMenuSubmissionNew . Just $ CSheetR tid ssh csh shn SShowR
|
||||
SubmissionOwnR -> i18nCrumb MsgMenuSubmissionOwn . Just $ CSheetR tid ssh csh shn SShowR
|
||||
SubmissionR cid sRoute' -> case sRoute' of
|
||||
SubShowR -> do
|
||||
mayList <- hasReadAccessTo $ CSheetR tid ssh csh shn SSubsR
|
||||
if
|
||||
| mayList
|
||||
-> i18nCrumb MsgBreadcrumbSubmission . Just $ CSheetR tid ssh csh shn SSubsR
|
||||
| otherwise
|
||||
-> i18nCrumb MsgBreadcrumbSubmission . Just $ CSheetR tid ssh csh shn SShowR
|
||||
CorrectionR -> i18nCrumb MsgMenuCorrection . Just $ CSubmissionR tid ssh csh shn cid SubShowR
|
||||
SubDelR -> i18nCrumb MsgMenuSubmissionDelete . Just $ CSubmissionR tid ssh csh shn cid SubShowR
|
||||
SubAssignR -> i18nCrumb MsgCorrectorAssignTitle . Just $ CSubmissionR tid ssh csh shn cid SubShowR
|
||||
SInviteR -> i18nCrumb MsgBreadcrumbSubmissionUserInvite . Just $ CSubmissionR tid ssh csh shn cid SubShowR
|
||||
SubArchiveR sft -> i18nCrumb sft . Just $ CSubmissionR tid ssh csh shn cid SubShowR
|
||||
SubDownloadR _ _ -> i18nCrumb MsgBreadcrumbSubmissionFile . Just $ CSubmissionR tid ssh csh shn cid SubShowR
|
||||
SCorrR -> i18nCrumb MsgMenuCorrectors . Just $ CSheetR tid ssh csh shn SShowR
|
||||
SArchiveR -> i18nCrumb MsgBreadcrumbSheetArchive . Just $ CSheetR tid ssh csh shn SShowR
|
||||
SIsCorrR -> i18nCrumb MsgBreadcrumbSheetIsCorrector . Just $ CSheetR tid ssh csh shn SShowR
|
||||
SPseudonymR -> i18nCrumb MsgBreadcrumbSheetPseudonym . Just $ CSheetR tid ssh csh shn SShowR
|
||||
SCorrInviteR -> i18nCrumb MsgBreadcrumbSheetCorrectorInvite . Just $ CSheetR tid ssh csh shn SShowR
|
||||
SZipR sft -> i18nCrumb sft . Just $ CSheetR tid ssh csh shn SShowR
|
||||
SFileR _ _ -> i18nCrumb MsgBreadcrumbSheetFile . Just $ CSheetR tid ssh csh shn SShowR
|
||||
|
||||
breadcrumb (CourseR tid ssh csh MaterialListR) = i18nCrumb MsgMenuMaterialList . Just $ CourseR tid ssh csh CShowR
|
||||
breadcrumb (CourseR tid ssh csh MaterialNewR ) = i18nCrumb MsgMenuMaterialNew . Just $ CourseR tid ssh csh MaterialListR
|
||||
breadcrumb (CourseR tid ssh csh (MaterialR mnm sRoute)) = case sRoute of
|
||||
MShowR -> maybeT (i18nCrumb MsgBreadcrumbMaterial . Just $ CourseR tid ssh csh MaterialListR) $ do
|
||||
guardM . hasReadAccessTo $ CMaterialR tid ssh csh mnm MShowR
|
||||
return (original mnm, Just $ CourseR tid ssh csh MaterialListR)
|
||||
MEditR -> i18nCrumb MsgMenuMaterialEdit . Just $ CMaterialR tid ssh csh mnm MShowR
|
||||
MDelR -> i18nCrumb MsgMenuMaterialDelete . Just $ CMaterialR tid ssh csh mnm MShowR
|
||||
MArchiveR -> i18nCrumb MsgBreadcrumbMaterialArchive . Just $ CMaterialR tid ssh csh mnm MShowR
|
||||
MFileR _ -> i18nCrumb MsgBreadcrumbMaterialFile . Just $ CMaterialR tid ssh csh mnm MShowR
|
||||
|
||||
breadcrumb CorrectionsR = i18nCrumb MsgMenuCorrections Nothing
|
||||
breadcrumb CorrectionsUploadR = i18nCrumb MsgMenuCorrectionsUpload $ Just CorrectionsR
|
||||
breadcrumb CorrectionsCreateR = i18nCrumb MsgMenuCorrectionsCreate $ Just CorrectionsR
|
||||
breadcrumb CorrectionsGradeR = i18nCrumb MsgMenuCorrectionsGrade $ Just CorrectionsR
|
||||
breadcrumb CorrectionsDownloadR = i18nCrumb MsgMenuCorrectionsDownload $ Just CorrectionsR
|
||||
|
||||
breadcrumb (CryptoUUIDDispatchR _) = i18nCrumb MsgBreadcrumbCryptoIDDispatch Nothing
|
||||
|
||||
breadcrumb (MessageR _) = do
|
||||
mayList <- (== Authorized) <$> evalAccess MessageListR False
|
||||
return $ if
|
||||
| mayList -> ("Statusmeldung", Just MessageListR)
|
||||
| otherwise -> ("Statusmeldung", Just HomeR)
|
||||
breadcrumb (MessageListR) = return ("Statusmeldungen", Just AdminR)
|
||||
breadcrumb _ = return ("Uni2work", Nothing) -- Default is no breadcrumb at all
|
||||
if
|
||||
| mayList -> i18nCrumb MsgBreadcrumbSystemMessage $ Just MessageListR
|
||||
| otherwise -> i18nCrumb MsgBreadcrumbSystemMessage $ Just HomeR
|
||||
breadcrumb MessageListR = i18nCrumb MsgMenuMessageList $ Just AdminR
|
||||
|
||||
breadcrumb GlossaryR = i18nCrumb MsgMenuGlossary $ Just InfoR
|
||||
-- breadcrumb _ = return ("Uni2work", Nothing) -- Default is no breadcrumb at all
|
||||
|
||||
submissionList :: TermId -> CourseShorthand -> SheetName -> UserId -> DB [E.Value SubmissionId]
|
||||
submissionList tid csh shn uid = E.select . E.from $ \(course `E.InnerJoin` sheet `E.InnerJoin` submission `E.InnerJoin` submissionUser) -> do
|
||||
@ -2063,6 +2278,14 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the
|
||||
, menuItemModal = False
|
||||
, menuItemAccessCallback' = return True
|
||||
}
|
||||
, return MenuItem
|
||||
{ menuItemType = Footer
|
||||
, menuItemLabel = MsgMenuGlossary
|
||||
, menuItemIcon = Nothing
|
||||
, menuItemRoute = SomeRoute GlossaryR
|
||||
, menuItemModal = False
|
||||
, menuItemAccessCallback' = return True
|
||||
}
|
||||
, do
|
||||
mCurrentRoute <- getCurrentRoute
|
||||
|
||||
@ -2309,6 +2532,14 @@ pageActions (InfoR) = [
|
||||
, menuItemModal = False
|
||||
, menuItemAccessCallback' = return True
|
||||
}
|
||||
, MenuItem
|
||||
{ menuItemType = PageActionPrime
|
||||
, menuItemLabel = MsgMenuGlossary
|
||||
, menuItemIcon = Nothing
|
||||
, menuItemRoute = SomeRoute GlossaryR
|
||||
, menuItemModal = False
|
||||
, menuItemAccessCallback' = return True
|
||||
}
|
||||
]
|
||||
pageActions (VersionR) = [
|
||||
MenuItem
|
||||
@ -3464,7 +3695,7 @@ upsertCampusUser ldapData Creds{..} = do
|
||||
, userWarningDays = userDefaultWarningDays
|
||||
, userShowSex = userDefaultShowSex
|
||||
, userNotificationSettings = def
|
||||
, userMailLanguages = def
|
||||
, userLanguages = Nothing
|
||||
, userCsvOptions = def
|
||||
, userTokensIssuedAfter = Nothing
|
||||
, userCreated = now
|
||||
@ -3584,6 +3815,47 @@ associateUserSchoolsByTerms uid = do
|
||||
, userSchoolIsOptOut = False
|
||||
}
|
||||
|
||||
setLangCookie :: MonadHandler m => Lang -> m ()
|
||||
setLangCookie lang = do
|
||||
now <- liftIO getCurrentTime
|
||||
setCookie $ def
|
||||
{ setCookieName = "_LANG"
|
||||
, setCookieValue = encodeUtf8 lang
|
||||
, setCookieExpires = Just $ addUTCTime (400 * avgNominalYear) now
|
||||
, setCookiePath = Just "/"
|
||||
}
|
||||
|
||||
updateUserLanguage :: Maybe Lang -> DB (Maybe Lang)
|
||||
updateUserLanguage (Just lang) = do
|
||||
unless (lang `elem` appLanguages) $
|
||||
invalidArgs ["Unsupported language"]
|
||||
|
||||
muid <- maybeAuthId
|
||||
for_ muid $ \uid -> do
|
||||
langs <- languages
|
||||
update uid [ UserLanguages =. Just (Languages $ lang : nub (filter ((&&) <$> (`elem` appLanguages) <*> (/= lang)) langs)) ]
|
||||
setLangCookie lang
|
||||
return $ Just lang
|
||||
updateUserLanguage Nothing = runMaybeT $ do
|
||||
uid <- MaybeT maybeAuthId
|
||||
User{..} <- MaybeT $ get uid
|
||||
setLangs <- toList . selectLanguages appLanguages <$> languages
|
||||
highPrioSetLangs <- toList . selectLanguages appLanguages <$> highPrioRequestedLangs
|
||||
let userLanguages' = toList . selectLanguages appLanguages <$> userLanguages ^? _Just . _Wrapped
|
||||
lang <- case (userLanguages', setLangs, highPrioSetLangs) of
|
||||
(_, _, hpl : _)
|
||||
-> lift $ hpl <$ update uid [ UserLanguages =. Just (Languages highPrioSetLangs) ]
|
||||
(Just (l : _), _, _)
|
||||
-> return l
|
||||
(Nothing, l : _, _)
|
||||
-> lift $ l <$ update uid [ UserLanguages =. Just (Languages setLangs) ]
|
||||
(Just [], l : _, _)
|
||||
-> return l
|
||||
(_, [], _)
|
||||
-> mzero
|
||||
setLangCookie lang
|
||||
return lang
|
||||
|
||||
|
||||
instance YesodAuth UniWorX where
|
||||
type AuthId UniWorX = UserId
|
||||
@ -3675,13 +3947,21 @@ instance YesodAuth UniWorX where
|
||||
|
||||
authHttpManager = getsYesod appHttpManager
|
||||
|
||||
onLogin = addMessageI Success Auth.NowLoggedIn
|
||||
onLogin = liftHandler $ do
|
||||
mlang <- runDB $ updateUserLanguage Nothing
|
||||
app <- getYesod
|
||||
let mr | Just lang <- mlang = renderMessage app . map (Text.intercalate "-") . reverse . inits $ Text.splitOn "-" lang
|
||||
| otherwise = renderMessage app []
|
||||
addMessage Success . toHtml $ mr Auth.NowLoggedIn
|
||||
|
||||
onErrorHtml dest msg = do
|
||||
addMessage Error $ toHtml msg
|
||||
redirect dest
|
||||
|
||||
renderAuthMessage _ _ = Auth.germanMessage -- TODO
|
||||
renderAuthMessage _ ls = case lang of
|
||||
("en" : _) -> Auth.englishMessage
|
||||
_other -> Auth.germanMessage
|
||||
where lang = Text.splitOn "-" $ selectLanguage' appLanguages ls
|
||||
|
||||
instance YesodAuthPersist UniWorX
|
||||
|
||||
|
||||
@ -33,10 +33,7 @@ getAdminR :: Handler Html
|
||||
getAdminR = -- do
|
||||
siteLayoutMsg MsgAdminHeading $ do
|
||||
setTitleI MsgAdminHeading
|
||||
[whamlet|
|
||||
This shall become the Administrators' overview page.
|
||||
Its current purpose is to provide links to some important admin functions
|
||||
|]
|
||||
i18n MsgAdminPageEmpty
|
||||
|
||||
-- BEGIN - Buttons needed only here
|
||||
data ButtonCreate = CreateMath | CreateInf -- Dummy for Example
|
||||
@ -58,7 +55,7 @@ emailTestForm :: AForm (HandlerFor UniWorX) (Email, MailContext)
|
||||
emailTestForm = (,)
|
||||
<$> areq emailField (fslI MsgMailTestFormEmail) Nothing
|
||||
<*> ( MailContext
|
||||
<$> (MailLanguages <$> areq (reorderField appLanguagesOpts) (fslI MsgMailTestFormLanguages) Nothing)
|
||||
<$> (Languages <$> areq (reorderField appLanguagesOpts) (fslI MsgMailTestFormLanguages) Nothing)
|
||||
<*> (toMailDateTimeFormat
|
||||
<$> areq (selectField $ dateTimeFormatOptions SelFormatDateTime) (fslI MsgDateTimeFormat) Nothing
|
||||
<*> areq (selectField $ dateTimeFormatOptions SelFormatDate) (fslI MsgDateFormat) Nothing
|
||||
@ -74,7 +71,7 @@ emailTestForm = (,)
|
||||
makeDemoForm :: Int -> Form (Int,Bool,Double)
|
||||
makeDemoForm n = identifyForm ("adminTestForm" :: Text) $ \html -> do
|
||||
(result, widget) <- flip (renderAForm FormStandard) html $ (,,)
|
||||
<$> areq (minIntField n "Zahl") (fromString $ "Ganzzahl > " ++ show n) Nothing
|
||||
<$> areq (minIntFieldI n ("Zahl" :: Text)) (fromString $ "Ganzzahl > " ++ show n) Nothing
|
||||
<* aformSection MsgFormBehaviour
|
||||
<*> areq checkBoxField "Muss nächste Zahl größer sein?" (Just True)
|
||||
<*> areq doubleField "Fliesskommazahl" Nothing
|
||||
@ -214,7 +211,7 @@ postAdminTestR = do
|
||||
siteLayout locallyDefinedPageHeading $ do
|
||||
-- defaultLayout $ do
|
||||
setTitle "Uni2work Admin Testpage"
|
||||
$(widgetFile "adminTest")
|
||||
$(i18nWidgetFile "admin-test")
|
||||
|
||||
[whamlet|<h2>Formular Demonstration|]
|
||||
wrapForm formWidget FormSettings
|
||||
@ -254,8 +251,9 @@ postAdminTestR = do
|
||||
getAdminErrMsgR, postAdminErrMsgR :: Handler Html
|
||||
getAdminErrMsgR = postAdminErrMsgR
|
||||
postAdminErrMsgR = do
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
((ctResult, ctView), ctEncoding) <- runFormPost . renderAForm FormStandard $
|
||||
unTextarea <$> areq textareaField (fslpI MsgErrMsgCiphertext "Ciphertext") Nothing
|
||||
unTextarea <$> areq textareaField (fslpI MsgErrMsgCiphertext (mr MsgErrMsgCiphertext)) Nothing
|
||||
|
||||
plaintext <- formResultMaybe ctResult $ exceptT (\err -> Nothing <$ addMessageI Error err) (return . Just) . (encodedSecretBoxOpen :: Text -> ExceptT EncodedSecretBoxException Handler Value)
|
||||
|
||||
|
||||
@ -605,7 +605,7 @@ postCorrectionsR = do
|
||||
, prismAForm (singletonFilter "term" ) mPrev $ aopt (lift `hoistField` selectField termOptions) (fslI MsgTerm)
|
||||
, prismAForm (singletonFilter "school" ) mPrev $ aopt (lift `hoistField` selectField schoolOptions) (fslI MsgCourseSchool)
|
||||
, Map.singleton "sheet-search" . maybeToList <$> aopt (lift `hoistField` textField) (fslI MsgSheet) (Just <$> listToMaybe =<< ((Map.lookup "sheet-search" =<< mPrev) <|> (Map.lookup "sheet" =<< mPrev)))
|
||||
, prismAForm (singletonFilter "israted" . maybePrism _PathPiece) mPrev $ aopt boolField (fslI MsgRatingTime)
|
||||
, prismAForm (singletonFilter "israted" . maybePrism _PathPiece) mPrev $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgRatingTime)
|
||||
]
|
||||
courseOptions = runDB $ do
|
||||
courses <- selectList [] [Asc CourseShorthand] >>= filterM (\(Entity _ Course{..}) -> (== Authorized) <$> evalAccessCorrector courseTerm courseSchool courseShorthand)
|
||||
@ -650,8 +650,8 @@ postCCorrectionsR tid ssh csh = do
|
||||
-- "pseudonym" TODO DB only stores Word24
|
||||
, Map.singleton "sheet-search" . maybeToList <$> aopt textField (fslI MsgSheet) (Just <$> listToMaybe =<< ((Map.lookup "sheet-search" =<< mPrev) <|> (Map.lookup "sheet" =<< mPrev)))
|
||||
, prismAForm (singletonFilter "corrector-name-email") mPrev $ aopt textField (fslI MsgCorrector)
|
||||
, prismAForm (singletonFilter "isassigned" . maybePrism _PathPiece) mPrev $ aopt boolField (fslI MsgHasCorrector)
|
||||
, prismAForm (singletonFilter "israted" . maybePrism _PathPiece) mPrev $ aopt boolField (fslI MsgRatingTime)
|
||||
, prismAForm (singletonFilter "isassigned" . maybePrism _PathPiece) mPrev $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgHasCorrector)
|
||||
, prismAForm (singletonFilter "israted" . maybePrism _PathPiece) mPrev $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgRatingTime)
|
||||
]
|
||||
psValidator = def & defaultPagesize PagesizeAll -- Assisstant always want to see them all at once anyway
|
||||
correctionsR whereClause colonnade filterUI psValidator $ Map.fromList
|
||||
@ -681,8 +681,8 @@ postSSubsR tid ssh csh shn = do
|
||||
[ prismAForm (singletonFilter "user-name-email") mPrev $ aopt textField (fslI MsgCourseMembers)
|
||||
, prismAForm (singletonFilter "user-matriclenumber") mPrev $ aopt textField (fslI MsgMatrikelNr)
|
||||
, prismAForm (singletonFilter "corrector-name-email") mPrev $ aopt textField (fslI MsgCorrector)
|
||||
, prismAForm (singletonFilter "isassigned" . maybePrism _PathPiece) mPrev $ aopt boolField (fslI MsgHasCorrector)
|
||||
, prismAForm (singletonFilter "israted" . maybePrism _PathPiece) mPrev $ aopt boolField (fslI MsgRatingTime)
|
||||
, prismAForm (singletonFilter "isassigned" . maybePrism _PathPiece) mPrev $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgHasCorrector)
|
||||
, prismAForm (singletonFilter "israted" . maybePrism _PathPiece) mPrev $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgRatingTime)
|
||||
-- "pseudonym" TODO DB only stores Word24
|
||||
]
|
||||
psValidator = def & defaultPagesize PagesizeAll -- Assisstant always want to see them all at once anyway
|
||||
@ -714,13 +714,14 @@ postCorrectionR tid ssh csh shn cid = do
|
||||
|
||||
results <- runDB $ correctionData tid ssh csh shn sub
|
||||
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
case results of
|
||||
[(Entity _ Course{..}, Entity _ Sheet{..}, Entity _ subm@Submission{..}, corrector)] -> do
|
||||
let ratingComment = fmap Text.strip submissionRatingComment >>= (\c -> c <$ guard (not $ null c))
|
||||
pointsForm = case sheetType of
|
||||
NotGraded -> pure Nothing
|
||||
_otherwise -> aopt (pointsFieldMax $ preview (_grading . _maxPoints) sheetType)
|
||||
(fslpI MsgRatingPoints "Punktezahl" & setTooltip sheetType)
|
||||
(fslpI MsgRatingPoints (mr MsgPointsPlaceholder) & setTooltip sheetType)
|
||||
(Just submissionRatingPoints)
|
||||
|
||||
((corrResult, corrForm'), corrEncoding) <- runFormPost . identifyForm FIDcorrection . renderAForm FormStandard $ (,,)
|
||||
@ -776,7 +777,6 @@ postCorrectionR tid ssh csh shn cid = do
|
||||
addMessageI Success MsgRatingFilesUpdated
|
||||
redirect $ CSubmissionR tid ssh csh shn cid CorrectionR
|
||||
|
||||
mr <- getMessageRender
|
||||
let sheetTypeDesc = mr sheetType
|
||||
heading = MsgCorrectionHead tid ssh csh shn cid
|
||||
headingWgt = [whamlet|
|
||||
@ -868,9 +868,10 @@ postCorrectionsCreateR = do
|
||||
, optionInternalValue = sid
|
||||
, optionExternalValue = toPathPiece (cID :: CryptoUUIDSheet)
|
||||
}
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
((pseudonymRes, pseudonymWidget), pseudonymEncoding) <- runFormPost . renderAForm FormStandard $ (,)
|
||||
<$> areq (selectField sheetOptions) (fslI MsgPseudonymSheet) Nothing
|
||||
<*> (textToList <$> areq textareaField (fslpI MsgCorrectionPseudonyms "Pseudonyme" & setTooltip MsgCorrectionPseudonymsTip) Nothing)
|
||||
<*> (textToList <$> areq textareaField (fslpI MsgCorrectionPseudonyms (mr MsgPseudonyms) & setTooltip MsgCorrectionPseudonymsTip) Nothing)
|
||||
|
||||
case pseudonymRes of
|
||||
FormMissing -> return ()
|
||||
|
||||
@ -256,7 +256,6 @@ makeCourseForm miButtonAction template = identifyForm FIDcourse . validateFormDB
|
||||
|
||||
optionalActionW' (bool mforcedJust mpopt mayChange) allocationForm' (fslI MsgCourseAllocationParticipate & setTooltip MsgCourseAllocationParticipateTip) (is _Just . cfAllocation <$> template)
|
||||
|
||||
-- TODO: internationalization
|
||||
-- let autoUnzipInfo = [|Entpackt hochgeladene Zip-Dateien (*.zip) automatisch und fügt den Inhalt dem Stamm-Verzeichnis der Abgabe hinzu. TODO|]
|
||||
|
||||
(result, widget) <- flip (renderAForm FormStandard) html $ CourseForm
|
||||
@ -267,9 +266,9 @@ makeCourseForm miButtonAction template = identifyForm FIDcourse . validateFormDB
|
||||
& setTooltip MsgCourseShorthandUnique) (cfShort <$> template)
|
||||
<*> areq (schoolFieldFor userSchools) (fslI MsgCourseSchool) (cfSchool <$> template)
|
||||
<*> areq termsField (fslI MsgCourseSemester) (cfTerm <$> template)
|
||||
<*> aopt htmlField (fslpI MsgCourseDescription "Bitte mindestens die Modulbeschreibung angeben"
|
||||
<*> aopt htmlField (fslpI MsgCourseDescription (mr MsgCourseDescriptionPlaceholder)
|
||||
& setTooltip MsgCourseDescriptionTip) (cfDesc <$> template)
|
||||
<*> aopt (urlField & cfStrip) (fslpI MsgCourseHomepageExternal "Optionale externe URL")
|
||||
<*> aopt (urlField & cfStrip) (fslpI MsgCourseHomepageExternal (mr MsgCourseHomepageExternalPlaceholder))
|
||||
(cfLink <$> template)
|
||||
<*> apopt checkBoxField (fslI MsgMaterialFree) (cfMatFree <$> template)
|
||||
<* aformSection MsgCourseFormSectionRegistration
|
||||
|
||||
@ -176,7 +176,7 @@ makeCourseTable whereClause colChoices psValidator = do
|
||||
, Just $ prismAForm (singletonFilter "lecturer") mPrev $ aopt textField (fslI MsgCourseLecturer)
|
||||
, Just $ prismAForm (singletonFilter "search") mPrev $ aopt textField (fslI MsgCourseFilterSearch)
|
||||
, Just $ prismAForm (singletonFilter "openregistration" . maybePrism _PathPiece) mPrev $ fmap (\x -> if isJust x && not (fromJust x) then Nothing else x) . aopt checkBoxField (fslI MsgCourseRegisterOpen)
|
||||
, muid $> prismAForm (singletonFilter "registered" . maybePrism _PathPiece) mPrev (aopt boolField (fslI MsgCourseFilterRegistered))
|
||||
, muid $> prismAForm (singletonFilter "registered" . maybePrism _PathPiece) mPrev (aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgCourseFilterRegistered))
|
||||
]
|
||||
, dbtStyle = def
|
||||
{ dbsFilterLayout = defaultDBSFilterLayout
|
||||
|
||||
@ -66,7 +66,7 @@ instance (CryptoRoute ciphertext plaintext, Dispatch ciphertext ps) => Dispatch
|
||||
|
||||
|
||||
getCryptoUUIDDispatchR :: UUID -> Handler ()
|
||||
getCryptoUUIDDispatchR uuid = dispatchID p uuid >>= maybe notFound (redirectWith found302)
|
||||
getCryptoUUIDDispatchR uuid = dispatchID p uuid >>= maybe notFound (redirectAccessWith movedPermanently301)
|
||||
where
|
||||
p :: Proxy '[ SubmissionId
|
||||
, UserId
|
||||
@ -74,7 +74,7 @@ getCryptoUUIDDispatchR uuid = dispatchID p uuid >>= maybe notFound (redirectWith
|
||||
p = Proxy
|
||||
|
||||
getCryptoFileNameDispatchR :: CI FilePath -> Handler ()
|
||||
getCryptoFileNameDispatchR path = dispatchID p path >>= maybe notFound (redirectWith found302)
|
||||
getCryptoFileNameDispatchR path = dispatchID p path >>= maybe notFound (redirectAccessWith movedPermanently301)
|
||||
where
|
||||
p :: Proxy '[ SubmissionId ]
|
||||
p = Proxy
|
||||
|
||||
@ -369,7 +369,7 @@ postEGradesR tid ssh csh examn = do
|
||||
, fltrStudyDegreeUI
|
||||
, fltrStudyFeaturesSemesterUI
|
||||
, fltrExamResultPointsUI examShowGrades
|
||||
, \mPrev -> prismAForm (singletonFilter "is-synced" . maybePrism _PathPiece) mPrev $ aopt boolField (fslI MsgExamUserSynchronised)
|
||||
, \mPrev -> prismAForm (singletonFilter "is-synced" . maybePrism _PathPiece) mPrev $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgExamUserSynchronised)
|
||||
]
|
||||
dbtStyle = def { dbsFilterLayout = defaultDBSFilterLayout }
|
||||
dbtParams = DBParamsForm
|
||||
|
||||
@ -2,6 +2,10 @@ module Handler.Info where
|
||||
|
||||
import Import
|
||||
import Handler.Utils
|
||||
import Handler.Info.TH
|
||||
|
||||
import qualified Data.Map as Map
|
||||
import qualified Data.CaseInsensitive as CI
|
||||
|
||||
import Development.GitRev
|
||||
|
||||
@ -67,3 +71,18 @@ getInfoLecturerR =
|
||||
if currentTime > expiryTime
|
||||
then mempty
|
||||
else toWidget [whamlet| ^{iconTooltip tooltipNew (Just IconNew) False} |]
|
||||
|
||||
getGlossaryR :: Handler Html
|
||||
getGlossaryR =
|
||||
siteLayoutMsg' MsgGlossaryTitle $ do
|
||||
setTitleI MsgGlossaryTitle
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
let
|
||||
entries' = sortOn (CI.mk . view _2) $ do
|
||||
(k, v) <- Map.toList entries
|
||||
msg <- maybeToList $ Map.lookup k msgMap
|
||||
return (k, mr msg, v)
|
||||
$(widgetFile "glossary")
|
||||
where
|
||||
entries = $(i18nWidgetFiles "glossary")
|
||||
msgMap = $(glossaryTerms "glossary")
|
||||
|
||||
23
src/Handler/Info/TH.hs
Normal file
23
src/Handler/Info/TH.hs
Normal file
@ -0,0 +1,23 @@
|
||||
module Handler.Info.TH
|
||||
( glossaryTerms
|
||||
) where
|
||||
|
||||
import Import
|
||||
import Handler.Utils.I18n
|
||||
|
||||
import Language.Haskell.TH
|
||||
|
||||
import qualified Data.Char as Char
|
||||
|
||||
import qualified Data.Map.Strict as Map
|
||||
import qualified Data.Text as Text
|
||||
|
||||
|
||||
glossaryTerms :: FilePath -> Q Exp
|
||||
glossaryTerms basename = do
|
||||
translationsAvailable <- i18nWidgetFilesAvailable' basename
|
||||
let terms = Map.mapWithKey (\k _ -> "Msg" <> unPathPiece k) translationsAvailable
|
||||
[e|Map.fromList $(listE . map (\(int, msg) -> tupE [litE . stringL $ repack int, conE $ mkName msg]) $ Map.toList terms)|]
|
||||
where
|
||||
unPathPiece :: Text -> String
|
||||
unPathPiece = repack . mconcat . map (over _head Char.toUpper) . Text.splitOn "-"
|
||||
@ -5,6 +5,7 @@ module Handler.Profile
|
||||
, getUserNotificationR, postUserNotificationR
|
||||
, getSetDisplayEmailR, postSetDisplayEmailR
|
||||
, getCsvOptionsR, postCsvOptionsR
|
||||
, postLangR
|
||||
) where
|
||||
|
||||
import Import
|
||||
@ -22,6 +23,8 @@ import qualified Data.Set as Set
|
||||
import qualified Database.Esqueleto as E
|
||||
import qualified Database.Esqueleto.Utils as E
|
||||
-- import Database.Esqueleto ((^.))
|
||||
import qualified Data.Text as Text
|
||||
import Data.List (inits)
|
||||
|
||||
import qualified Data.CaseInsensitive as CI
|
||||
|
||||
@ -76,15 +79,16 @@ instance RenderMessage UniWorX NotificationTriggerKind where
|
||||
|
||||
makeSettingForm :: Maybe SettingsForm -> Form SettingsForm
|
||||
makeSettingForm template html = do
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
(result, widget) <- flip (renderAForm FormStandard) html $ SettingsForm
|
||||
<$ aformSection MsgFormPersonalAppearance
|
||||
<*> areq (textField & cfStrip) (fslI MsgUserDisplayName & setTooltip MsgUserDisplayNameRulesBelow) (stgDisplayName <$> template)
|
||||
<*> areq (emailField & cfStrip & cfCI) (fslI MsgUserDisplayEmail & setTooltip MsgUserDisplayEmailTip) (stgDisplayEmail <$> template)
|
||||
<* aformSection MsgFormCosmetics
|
||||
<*> areq (natFieldI $ MsgNatField "Favoriten")
|
||||
(fslpI MsgFavourites "Anzahl Favoriten" & setTooltip MsgFavouritesTip) (stgMaxFavourites <$> template)
|
||||
<*> areq (natFieldI $ MsgNatField "Favoriten-Semester")
|
||||
(fslpI MsgFavouriteSemesters "Anzahl Semester") (stgMaxFavouriteTerms <$> template)
|
||||
<*> areq (natFieldI MsgFavouritesNotNatural)
|
||||
(fslpI MsgFavourites (mr MsgFavouritesPlaceholder) & setTooltip MsgFavouritesTip) (stgMaxFavourites <$> template)
|
||||
<*> areq (natFieldI MsgFavouritesSemestersNotNatural)
|
||||
(fslpI MsgFavouriteSemesters (mr MsgFavouritesSemestersPlaceholder)) (stgMaxFavouriteTerms <$> template)
|
||||
<*> areq (selectField . return $ mkOptionList themeList)
|
||||
(fslI MsgTheme) { fsId = Just "theme-select" } (stgTheme <$> template)
|
||||
<*> areq (selectField $ dateTimeFormatOptions SelFormatDateTime) (fslI MsgDateTimeFormat) (stgDateTime <$> template)
|
||||
@ -315,7 +319,7 @@ postProfileR = do
|
||||
tResetTime <- traverse (formatTime SelFormatDateTime) userTokensIssuedAfter
|
||||
|
||||
siteLayout [whamlet|_{MsgProfileFor} ^{nameWidget userDisplayName userSurname}|] $ do
|
||||
setTitle . toHtml $ "Profil " <> userIdent
|
||||
setTitleI MsgProfileTitle
|
||||
let settingsForm =
|
||||
wrapForm formWidget FormSettings
|
||||
{ formMethod = POST
|
||||
@ -366,10 +370,11 @@ makeProfileData (Entity uid User{..}) = do
|
||||
submissionTable <- mkSubmissionTable uid -- Tabelle mit allen Abgaben und Abgabe-Gruppen
|
||||
submissionGroupTable <- mkSubmissionGroupTable uid -- Tabelle mit allen Abgabegruppen
|
||||
correctionsTable <- mkCorrectionsTable uid -- Tabelle mit allen Korrektor-Aufgaben
|
||||
let examTable = [whamlet|Prüfungen werden hier momentan leider noch nicht unterstützt.|]
|
||||
let ownTutorialTable = [whamlet|Übungsgruppen werden momentan leider noch nicht unterstützt.|]
|
||||
let tutorialTable = [whamlet|Übungsgruppen werden momentan leider noch nicht unterstützt.|]
|
||||
let examTable = [whamlet|_{MsgPersonalInfoExamAchievementsWip}|]
|
||||
let ownTutorialTable = [whamlet|_{MsgPersonalInfoOwnTutorialsWip}|]
|
||||
let tutorialTable = [whamlet|_{MsgPersonalInfoTutorialsWip}|]
|
||||
lastLogin <- traverse (formatTime SelFormatDateTime) userLastAuthentication
|
||||
let profileRemarks = $(i18nWidgetFile "profile-remarks")
|
||||
return $(widgetFile "profileData")
|
||||
|
||||
|
||||
@ -837,3 +842,18 @@ postCsvOptionsR = do
|
||||
, formEncoding = optionsEnctype
|
||||
, formAttrs = [ asyncSubmitAttr | isModal ]
|
||||
}
|
||||
|
||||
postLangR :: Handler ()
|
||||
postLangR = do
|
||||
((langRes, _), _) <- runFormPost $ identifyForm FIDLanguage langForm
|
||||
|
||||
formResult langRes $ \(lang, route) -> do
|
||||
lang' <- runDB . updateUserLanguage $ Just lang
|
||||
|
||||
app <- getYesod
|
||||
let mr | Just lang'' <- lang' = renderMessage app . map (Text.intercalate "-") . reverse . inits $ Text.splitOn "-" lang''
|
||||
| otherwise = renderMessage app []
|
||||
addMessage Success . toHtml $ mr MsgLanguageChanged
|
||||
redirect route
|
||||
|
||||
invalidArgs ["Language form required"]
|
||||
|
||||
@ -91,7 +91,7 @@ makeSheetForm msId template = identifyForm FIDsheet $ \html -> do
|
||||
oldFileIds <- (return.) <$> case msId of
|
||||
Nothing -> return $ partitionFileType mempty
|
||||
(Just sId) -> liftHandler $ runDB $ getFtIdMap sId
|
||||
mr <- getMsgRenderer
|
||||
mr'@(MsgRenderer mr) <- getMsgRenderer
|
||||
ctime <- ceilingQuarterHour <$> liftIO getCurrentTime
|
||||
(result, widget) <- flip (renderAForm FormStandard) html $ SheetForm
|
||||
<$> areq (textField & cfStrip & cfCI) (fslI MsgSheetName) (sfName <$> template)
|
||||
@ -103,9 +103,9 @@ makeSheetForm msId template = identifyForm FIDsheet $ \html -> do
|
||||
& setTooltip MsgSheetActiveFromTip)
|
||||
(sfActiveFrom <$> template)
|
||||
<*> areq utcTimeField (fslI MsgSheetActiveTo) (sfActiveTo <$> template)
|
||||
<*> aopt utcTimeField (fslpI MsgSheetHintFrom "Datum, sonst nur für Korrektoren"
|
||||
<*> aopt utcTimeField (fslpI MsgSheetHintFrom (mr MsgSheetHintFromPlaceholder)
|
||||
& setTooltip MsgSheetHintFromTip) (sfHintFrom <$> template)
|
||||
<*> aopt utcTimeField (fslpI MsgSheetSolutionFrom "Datum, sonst nur für Korrektoren"
|
||||
<*> aopt utcTimeField (fslpI MsgSheetSolutionFrom (mr MsgSheetSolutionFromPlaceholder)
|
||||
& setTooltip MsgSheetSolutionFromTip) (sfSolutionFrom <$> template)
|
||||
<* aformSection MsgSheetFormFiles
|
||||
<*> aopt (multiFileField $ oldFileIds SheetExercise) (fslI MsgSheetExercise) (sfSheetF <$> template)
|
||||
@ -124,7 +124,7 @@ makeSheetForm msId template = identifyForm FIDsheet $ \html -> do
|
||||
<*> aopt htmlField (fslpI MsgSheetMarking "Html") (sfMarkingText <$> template)
|
||||
return $ case result of
|
||||
FormSuccess sheetResult
|
||||
| errorMsgs <- validateSheet mr sheetResult
|
||||
| errorMsgs <- validateSheet mr' sheetResult
|
||||
, not $ null errorMsgs ->
|
||||
(FormFailure errorMsgs, widget)
|
||||
_ -> (result, widget)
|
||||
@ -923,5 +923,5 @@ postSCorrInviteR = invitationR correctorInvitationConfig
|
||||
getSIsCorrR :: TermId -> SchoolId -> CourseShorthand -> SheetName -> Handler Html
|
||||
-- NOTE: The route SIsCorrR is only used to verfify corrector access rights to given sheet!
|
||||
getSIsCorrR _ _ _ shn = do
|
||||
defaultLayout $ [whamlet|You have corrector access to #{shn}.|]
|
||||
defaultLayout . i18n $ MsgHaveCorrectorAccess shn
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ postMessageR cID = do
|
||||
Nothing -> (systemMessageSummary, systemMessageContent)
|
||||
Just SystemMessageTranslation{..} -> (systemMessageTranslationSummary, systemMessageTranslationContent)
|
||||
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
let
|
||||
mkForm = do
|
||||
((modifyRes, modifyView), modifyEnctype) <- runFormPost . identifyForm FIDSystemMessageModify . renderAForm FormStandard
|
||||
@ -31,9 +32,9 @@ postMessageR cID = do
|
||||
<*> aopt utcTimeField (fslI MsgSystemMessageTo) (Just systemMessageTo)
|
||||
<*> areq checkBoxField (fslI MsgSystemMessageAuthenticatedOnly) (Just systemMessageAuthenticatedOnly)
|
||||
<*> areq (selectField optionsFinite) (fslI MsgSystemMessageSeverity) (Just systemMessageSeverity)
|
||||
<*> areq (langField False) (fslpI MsgSystemMessageLanguage "RFC1766-Sprachcode") (Just systemMessageDefaultLanguage)
|
||||
<*> areq htmlField' (fslpI MsgSystemMessageContent "HTML") (Just systemMessageContent)
|
||||
<*> aopt htmlField' (fslpI MsgSystemMessageSummary "HTML") (Just systemMessageSummary)
|
||||
<*> areq (langField False) (fslpI MsgSystemMessageLanguage (mr MsgRFC1766)) (Just systemMessageDefaultLanguage)
|
||||
<*> areq htmlField' (fslpI MsgSystemMessageContent "Html") (Just systemMessageContent)
|
||||
<*> aopt htmlField' (fslpI MsgSystemMessageSummary "Html") (Just systemMessageSummary)
|
||||
|
||||
ts <- runDB $ selectList [ SystemMessageTranslationMessage ==. smId ] [Asc SystemMessageTranslationLanguage]
|
||||
let ts' = Map.fromList $ (systemMessageTranslationLanguage . entityVal &&& id) <$> ts
|
||||
@ -45,9 +46,9 @@ postMessageR cID = do
|
||||
<$> fmap (Entity tId)
|
||||
( SystemMessageTranslation
|
||||
<$> pure systemMessageTranslationMessage
|
||||
<*> areq (langField False) (fslpI MsgSystemMessageLanguage "RFC1766-Sprachcode") (Just systemMessageTranslationLanguage)
|
||||
<*> areq htmlField' (fslpI MsgSystemMessageContent "HTML") (Just systemMessageTranslationContent)
|
||||
<*> aopt htmlField' (fslpI MsgSystemMessageSummary "HTML") (Just systemMessageTranslationSummary)
|
||||
<*> areq (langField False) (fslpI MsgSystemMessageLanguage (mr MsgRFC1766)) (Just systemMessageTranslationLanguage)
|
||||
<*> areq htmlField' (fslpI MsgSystemMessageContent "Html") (Just systemMessageTranslationContent)
|
||||
<*> aopt htmlField' (fslpI MsgSystemMessageSummary "Html") (Just systemMessageTranslationSummary)
|
||||
)
|
||||
<*> combinedButtonFieldF ""
|
||||
|
||||
@ -56,9 +57,9 @@ postMessageR cID = do
|
||||
((addTransRes, addTransView), addTransEnctype) <- runFormPost . identifyForm FIDSystemMessageAddTranslation . renderAForm FormStandard
|
||||
$ SystemMessageTranslation
|
||||
<$> pure smId
|
||||
<*> areq (langField False) (fslpI MsgSystemMessageLanguage "RFC1766-Sprachcode") Nothing
|
||||
<*> areq htmlField' (fslpI MsgSystemMessageContent "HTML") Nothing
|
||||
<*> aopt htmlField' (fslpI MsgSystemMessageSummary "HTML") Nothing
|
||||
<*> areq (langField False) (fslpI MsgSystemMessageLanguage (mr MsgRFC1766)) Nothing
|
||||
<*> areq htmlField' (fslpI MsgSystemMessageContent "Html") Nothing
|
||||
<*> aopt htmlField' (fslpI MsgSystemMessageSummary "Html") Nothing
|
||||
|
||||
formResult modifyRes $ modifySystemMessage smId
|
||||
|
||||
@ -252,14 +253,15 @@ postMessageListR = do
|
||||
FormSuccess (_, _selection) -- prop> null _selection
|
||||
-> addMessageI Error MsgSystemMessageEmptySelection
|
||||
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
((addRes, addView), addEncoding) <- runFormPost . identifyForm FIDSystemMessageAdd . renderAForm FormStandard $ SystemMessage
|
||||
<$> aopt utcTimeField (fslI MsgSystemMessageFrom) Nothing
|
||||
<*> aopt utcTimeField (fslI MsgSystemMessageTo) Nothing
|
||||
<*> areq checkBoxField (fslI MsgSystemMessageAuthenticatedOnly) Nothing
|
||||
<*> areq (selectField optionsFinite) (fslI MsgSystemMessageSeverity) Nothing
|
||||
<*> areq (langField False) (fslpI MsgSystemMessageLanguage "RFC1766-Sprachcode") (Just $ NonEmpty.head appLanguages)
|
||||
<*> areq htmlField' (fslpI MsgSystemMessageContent "HTML") Nothing
|
||||
<*> aopt htmlField' (fslpI MsgSystemMessageSummary "HTML") Nothing
|
||||
<*> areq (langField False) (fslpI MsgSystemMessageLanguage (mr MsgRFC1766)) (Just $ NonEmpty.head appLanguages)
|
||||
<*> areq htmlField' (fslpI MsgSystemMessageContent "Html") Nothing
|
||||
<*> aopt htmlField' (fslpI MsgSystemMessageSummary "Html") Nothing
|
||||
|
||||
case addRes of
|
||||
FormMissing -> return ()
|
||||
|
||||
@ -7,6 +7,8 @@ import qualified Data.Map as Map
|
||||
import qualified Database.Esqueleto as E
|
||||
|
||||
import qualified Data.Set as Set
|
||||
|
||||
import qualified Control.Monad.State.Class as State
|
||||
|
||||
|
||||
-- | Default start day of term for season,
|
||||
@ -18,32 +20,15 @@ defaultDay True Summer = fromGregorian 2020 4 1
|
||||
defaultDay False Summer = fromGregorian 2020 9 30
|
||||
|
||||
|
||||
validateTerm :: Term -> [Text]
|
||||
validateTerm Term{..} =
|
||||
[ msg | (False, msg) <-
|
||||
[ --startOk
|
||||
( termStart `withinTerm` termName
|
||||
, "Jahreszahl im Namenskürzel stimmt nicht mit Semesterbeginn überein."
|
||||
)
|
||||
, -- endOk
|
||||
( termStart < termEnd
|
||||
, "Semester darf nicht enden, bevor es begann."
|
||||
)
|
||||
, -- startOk
|
||||
( termLectureStart < termLectureEnd
|
||||
, "Vorlesungszeit muss vor ihrem Ende anfgangen."
|
||||
)
|
||||
, -- lecStartOk
|
||||
( termStart <= termLectureStart
|
||||
, "Semester muss vor der Vorlesungszeit beginnen."
|
||||
)
|
||||
, -- lecEndOk
|
||||
( termEnd >= termLectureEnd
|
||||
, "Vorlesungszeit muss vor dem Semester enden."
|
||||
)
|
||||
] ]
|
||||
|
||||
|
||||
validateTerm :: (MonadHandler m, HandlerSite m ~ UniWorX)
|
||||
=> FormValidator Term m ()
|
||||
validateTerm = do
|
||||
Term{..} <- State.get
|
||||
guardValidation MsgTermStartMustMatchName $ termStart `withinTerm` termName
|
||||
guardValidation MsgTermEndMustBeAfterStart $ termStart < termEnd
|
||||
guardValidation MsgTermLectureEndMustBeAfterStart $ termLectureStart < termLectureEnd
|
||||
guardValidation MsgTermStartMustBeBeforeLectureStart $ termStart <= termLectureStart
|
||||
guardValidation MsgTermEndMustBeAfterLectureEnd $ termEnd >= termLectureEnd
|
||||
|
||||
|
||||
getTermShowR :: Handler TypedContent
|
||||
@ -66,22 +51,22 @@ getTermShowR = do
|
||||
provideRep $ toJSON . map fst <$> runDB (E.select $ E.from termData)
|
||||
provideRep $ do
|
||||
let colonnadeTerms = widgetColonnade $ mconcat
|
||||
[ sortable (Just "term-id") "Kürzel" $ \(Entity tid _, _) -> anchorCell
|
||||
[ sortable (Just "term-id") (i18nCell MsgTermShort) $ \(Entity tid _, _) -> anchorCell
|
||||
(TermCourseListR tid)
|
||||
[whamlet|#{toPathPiece tid}|]
|
||||
, sortable (Just "lecture-start") (i18nCell MsgLectureStart) $ \(Entity _ Term{..},_) ->
|
||||
cell $ formatTime SelFormatDate termLectureStart >>= toWidget
|
||||
, sortable (Just "lecture-end") "Ende Vorlesungen" $ \(Entity _ Term{..},_) ->
|
||||
, sortable (Just "lecture-end") (i18nCell MsgTermLectureEnd) $ \(Entity _ Term{..},_) ->
|
||||
cell $ formatTime SelFormatDate termLectureEnd >>= toWidget
|
||||
, sortable Nothing "Aktiv" $ \(Entity _ Term{..},_) ->
|
||||
, sortable Nothing (i18nCell MsgTermActive) $ \(Entity _ Term{..},_) ->
|
||||
tickmarkCell termActive
|
||||
, sortable Nothing "Kurse" $ \(_, E.Value numCourses) ->
|
||||
, sortable Nothing (i18nCell MsgTermCourseCount) $ \(_, E.Value numCourses) ->
|
||||
cell [whamlet|_{MsgNumCourses numCourses}|]
|
||||
, sortable (Just "start") "Semesteranfang" $ \(Entity _ Term{..},_) ->
|
||||
, sortable (Just "start") (i18nCell MsgTermStart) $ \(Entity _ Term{..},_) ->
|
||||
cell $ formatTime SelFormatDate termStart >>= toWidget
|
||||
, sortable (Just "end") "Semesterende" $ \(Entity _ Term{..},_) ->
|
||||
, sortable (Just "end") (i18nCell MsgTermEnd) $ \(Entity _ Term{..},_) ->
|
||||
cell $ formatTime SelFormatDate termEnd >>= toWidget
|
||||
, sortable Nothing "Feiertage im Semester" $ \(Entity _ Term{..},_) ->
|
||||
, sortable Nothing (i18nCell MsgTermHolidays) $ \(Entity _ Term{..},_) ->
|
||||
cell $ do
|
||||
termHolidays' <- mapM (formatTime SelFormatDate) termHolidays
|
||||
[whamlet|
|
||||
@ -248,7 +233,7 @@ termToTemplate (Just Term{..}) = TermFormTemplate
|
||||
}
|
||||
|
||||
newTermForm :: TermFormTemplate -> Form Term
|
||||
newTermForm template html = do
|
||||
newTermForm template = validateForm validateTerm $ \html -> do
|
||||
mr <- getMessageRender
|
||||
let
|
||||
tidForm
|
||||
@ -264,7 +249,7 @@ newTermForm template html = do
|
||||
(fslI MsgTermHolidays & setTooltip MsgMassInputTip)
|
||||
True
|
||||
(tftHolidays template)
|
||||
(result, widget) <- flip (renderAForm FormStandard) html $ Term
|
||||
flip (renderAForm FormStandard) html $ Term
|
||||
<$> tidForm
|
||||
<*> areq dayField (fslI MsgTermStartDay & setTooltip MsgTermStartDayTooltip) (tftStart template)
|
||||
<*> areq dayField (fslI MsgTermEndDay & setTooltip MsgTermEndDayTooltip) (tftEnd template)
|
||||
@ -272,24 +257,3 @@ newTermForm template html = do
|
||||
<*> areq dayField (fslI MsgTermLectureStart) (tftLectureStart template)
|
||||
<*> areq dayField (fslI MsgTermLectureEnd & setTooltip MsgTermLectureEndTooltip) (tftLectureEnd template)
|
||||
<*> areq checkBoxField (fslI MsgTermActive) (tftActive template)
|
||||
return $ case result of
|
||||
FormSuccess termResult
|
||||
| errorMsgs <- validateTerm termResult
|
||||
, not $ null errorMsgs ->
|
||||
(FormFailure errorMsgs,
|
||||
[whamlet|
|
||||
<div class="alert alert-danger">
|
||||
<div class="alert__content">
|
||||
<h4> Fehler:
|
||||
<ul>
|
||||
$forall errmsg <- errorMsgs
|
||||
<li> #{errmsg}
|
||||
^{widget}
|
||||
|]
|
||||
)
|
||||
_ -> (result, widget)
|
||||
{-
|
||||
where
|
||||
set :: Text -> FieldSettings site
|
||||
set = bfs
|
||||
-}
|
||||
|
||||
@ -390,7 +390,7 @@ postAdminUserR uuid = do
|
||||
}
|
||||
userDataWidget <- runDB $ makeProfileData $ Entity uid user
|
||||
siteLayout heading $ do
|
||||
let deleteWidget = $(widgetFile "widgets/data-delete/data-delete")
|
||||
let deleteWidget = $(i18nWidgetFile "data-delete")
|
||||
$(widgetFile "adminUser")
|
||||
|
||||
|
||||
|
||||
@ -76,7 +76,7 @@ postAdminUserAddR = do
|
||||
, userWarningDays = userDefaultWarningDays
|
||||
, userShowSex = userDefaultShowSex
|
||||
, userNotificationSettings = def
|
||||
, userMailLanguages = def
|
||||
, userLanguages = Nothing
|
||||
, userCsvOptions = def
|
||||
, userTokensIssuedAfter = Nothing
|
||||
, userCreated = now
|
||||
|
||||
@ -90,7 +90,7 @@ formatTimeW :: (HasLocalTime t) => SelDateTimeFormat -> t -> Widget
|
||||
formatTimeW s t = toWidget =<< formatTime s t
|
||||
|
||||
formatTimeMail :: (MonadMail m, HasLocalTime t) => SelDateTimeFormat -> t -> m Text
|
||||
formatTimeMail sel t = fmap fromString $ Time.formatTime <$> (getTimeLocale' . mailLanguages <$> askMailLanguages) <*> (unDateTimeFormat <$> askMailDateTimeFormat sel) <*> pure (toLocalTime t)
|
||||
formatTimeMail sel t = fmap fromString $ Time.formatTime <$> (getTimeLocale' . view _Wrapped <$> askMailLanguages) <*> (unDateTimeFormat <$> askMailDateTimeFormat sel) <*> pure (toLocalTime t)
|
||||
|
||||
getTimeLocale :: MonadHandler m => m TimeLocale
|
||||
getTimeLocale = getTimeLocale' <$> languages
|
||||
|
||||
@ -284,21 +284,12 @@ htmlField' = htmlField
|
||||
natFieldI :: (Monad m, Integral i, RenderMessage (HandlerSite m) msg, RenderMessage (HandlerSite m) FormMessage) => msg -> Field m i
|
||||
natFieldI msg = convertField fromInteger toInteger $ checkBool (>= 0) msg $ intMinField 0
|
||||
|
||||
natField :: (Monad m, Integral i, RenderMessage (HandlerSite m) FormMessage) => Text -> Field m i
|
||||
natField d = convertField fromInteger toInteger $ checkBool (>= 0) (T.append d " muss eine natürliche Zahl sein.") $ intMinField 0
|
||||
|
||||
natIntField ::(Monad m, RenderMessage (HandlerSite m) FormMessage) => Text -> Field m Integer
|
||||
natIntField = natField
|
||||
|
||||
posIntField :: (Monad m, Integral i, RenderMessage (HandlerSite m) FormMessage) => Text -> Field m i
|
||||
posIntField d = convertField fromInteger toInteger $ checkBool (> 0) (T.append d " muss eine positive Zahl sein.") $ intMinField 1
|
||||
|
||||
posIntFieldI :: (Monad m, Integral i, RenderMessage (HandlerSite m) msg, RenderMessage (HandlerSite m) FormMessage) => msg -> Field m i
|
||||
posIntFieldI msg = convertField fromInteger toInteger $ checkBool (> 0) msg $ intMinField 0
|
||||
|
||||
-- | Field to request integral number > 'm'
|
||||
minIntField :: (Monad m, Integral i, Show i, RenderMessage (HandlerSite m) FormMessage) => i -> Text -> Field m i
|
||||
minIntField m d = checkBool (> m) (T.concat [d," muss größer als ", T.pack $ show m, " sein."]) $ intMinField m
|
||||
minIntFieldI :: (Monad m, Integral i, Show i, RenderMessage (HandlerSite m) FormMessage, RenderMessage (HandlerSite m) msg) => i -> msg -> Field m i
|
||||
minIntFieldI m msg = checkBool (> m) msg $ intMinField m
|
||||
|
||||
pointsField :: (Monad m, HandlerSite m ~ UniWorX) => Field m Points
|
||||
pointsField = pointsFieldMinMax (Just 0) Nothing
|
||||
@ -831,7 +822,7 @@ sheetGroupAFormReq fs template = multiActionA selOptions fs (classify' <$> templ
|
||||
where
|
||||
selOptions = Map.fromList
|
||||
[ ( Arbitrary', Arbitrary
|
||||
<$> apreq (natField "Gruppengröße") (fslI MsgSheetGroupMaxGroupsize & noValidate) (preview _maxParticipants =<< template)
|
||||
<$> apreq (natFieldI MsgGroupSizeNotNatural) (fslI MsgSheetGroupMaxGroupsize & noValidate) (preview _maxParticipants =<< template)
|
||||
)
|
||||
, ( RegisteredGroups', pure RegisteredGroups )
|
||||
, ( NoGroups', pure NoGroups )
|
||||
@ -861,6 +852,10 @@ dayTimeField fs mutc = do
|
||||
| otherwise = (Nothing,Nothing)
|
||||
-}
|
||||
|
||||
fieldTimeFormat :: String
|
||||
-- fieldTimeFormat = "%e.%m.%y %k:%M"
|
||||
fieldTimeFormat = "%Y-%m-%dT%H:%M:%S"
|
||||
|
||||
localTimeField :: (MonadHandler m, HandlerSite m ~ UniWorX) => Field m LocalTime
|
||||
localTimeField = Field
|
||||
{ fieldParse = parseHelperGen readTime
|
||||
@ -873,11 +868,7 @@ localTimeField = Field
|
||||
, fieldEnctype = UrlEncoded
|
||||
}
|
||||
where
|
||||
fieldTimeFormat :: String
|
||||
--fieldTimeFormat = "%e.%m.%y %k:%M"
|
||||
fieldTimeFormat = "%Y-%m-%dT%H:%M:%S"
|
||||
|
||||
-- `defaultTimeLocale` is okay here, since `fieldTimeFormat` does not contain any
|
||||
-- `defaultTimeLocale` is okay here, since `fieldTimeFormat` does not contain any words
|
||||
readTime :: Text -> Either UniWorXMessage LocalTime
|
||||
readTime t =
|
||||
case parseTimeM True defaultTimeLocale fieldTimeFormat (T.unpack t) of
|
||||
@ -925,8 +916,8 @@ jsonField hide = Field{..}
|
||||
boolField :: ( MonadHandler m
|
||||
, HandlerSite m ~ UniWorX
|
||||
)
|
||||
=> Field m Bool
|
||||
boolField = Field
|
||||
=> Maybe (SomeMessage UniWorX) -> Field m Bool
|
||||
boolField mkNone = Field
|
||||
{ fieldParse = \e _ -> return $ boolParser e
|
||||
, fieldView = \theId name attrs val isReq -> $(widgetFile "widgets/fields/bool")
|
||||
, fieldEnctype = UrlEncoded
|
||||
@ -1150,7 +1141,7 @@ multiUserField onlySuggested suggestions = Field{..}
|
||||
case dbRes of
|
||||
[] -> return $ Left email
|
||||
[E.Value uid] -> return $ Right uid
|
||||
_other -> throwE $ SomeMessage ("Ambiguous e-mail addr" :: Text)
|
||||
_other -> throwE $ SomeMessage MsgAmbiguousEmail
|
||||
|
||||
examResultField :: forall m res.
|
||||
( MonadHandler m
|
||||
@ -1244,13 +1235,14 @@ csvFormatOptionsForm fs mPrev = hoistAForm liftHandler . multiActionA csvActs fs
|
||||
MsgRenderer mr <- getMsgRenderer
|
||||
let
|
||||
opts =
|
||||
[ (MsgCsvDelimiterNull, '\0')
|
||||
, (MsgCsvDelimiterTab, '\t')
|
||||
, (MsgCsvDelimiterComma, ',')
|
||||
, (MsgCsvDelimiterColon, chr 58)
|
||||
, (MsgCsvDelimiterBar, '|')
|
||||
, (MsgCsvDelimiterSpace, ' ')
|
||||
, (MsgCsvDelimiterUnitSep, chr 31)
|
||||
[ (MsgCsvDelimiterNull, '\0')
|
||||
, (MsgCsvDelimiterTab, '\t')
|
||||
, (MsgCsvDelimiterComma, ',')
|
||||
, (MsgCsvDelimiterColon, chr 58)
|
||||
, (MsgCsvDelimiterSemicolon, chr 59)
|
||||
, (MsgCsvDelimiterBar, '|')
|
||||
, (MsgCsvDelimiterSpace, ' ')
|
||||
, (MsgCsvDelimiterUnitSep, chr 31)
|
||||
]
|
||||
olReadExternal t = do
|
||||
i <- readMay t
|
||||
|
||||
@ -1,14 +1,22 @@
|
||||
module Handler.Utils.I18n
|
||||
where
|
||||
( i18nWidgetFile
|
||||
, i18nWidgetFilesAvailable, i18nWidgetFilesAvailable', i18nWidgetFiles
|
||||
) where
|
||||
|
||||
import Import
|
||||
|
||||
import Language.Haskell.TH
|
||||
import Language.Haskell.TH.Syntax (qRunIO)
|
||||
import qualified Language.Haskell.TH.Syntax as TH
|
||||
|
||||
import qualified Data.List as List
|
||||
import qualified Data.List.NonEmpty as NonEmpty
|
||||
|
||||
import qualified Data.Set as Set
|
||||
import qualified Data.Map as Map
|
||||
|
||||
import qualified Data.Text as Text
|
||||
|
||||
import System.Directory (listDirectory)
|
||||
import System.FilePath.Posix (takeBaseName)
|
||||
|
||||
@ -40,4 +48,31 @@ i18nWidgetFile basename = do
|
||||
[ funD ws $ [ clause [litP $ stringL l] (normalB . widgetFile $ "i18n" </> basename </> l) []
|
||||
| l <- unpack <$> NonEmpty.toList availableTranslations' -- One function definition for every available language
|
||||
] ++ [ clause [wildP] (normalB [e| error "selectLanguage returned an invalid translation" |]) [] ] -- Fallback mostly there so compiler does not complain about non-exhaustive pattern match
|
||||
] [e|selectLanguage availableTranslations' >>= $(varE ws)|]
|
||||
] [e|selectLanguage availableTranslations' >>= $(varE ws)|]
|
||||
|
||||
i18nWidgetFilesAvailable' :: FilePath -> Q (Map Text (NonEmpty Text))
|
||||
i18nWidgetFilesAvailable' basename = do
|
||||
let i18nDirectory = "templates" </> "i18n" </> basename
|
||||
availableFiles <- qRunIO $ listDirectory i18nDirectory
|
||||
let fileKinds' = fmap (pack . dropExtension . takeBaseName &&& toTranslation . pack . takeBaseName) availableFiles
|
||||
fileKinds :: Map Text [Text]
|
||||
fileKinds = sortWith (NTop . flip List.elemIndex (NonEmpty.toList appLanguages)) . Set.toList <$> Map.fromListWith Set.union [ (kind, Set.singleton l) | (kind, Just l) <- fileKinds' ]
|
||||
toTranslation fName = listToMaybe . sortOn length . mapMaybe (flip Text.stripPrefix fName . (<>".")) $ map fst fileKinds'
|
||||
|
||||
iforM fileKinds $ \kind -> maybe (fail $ "‘" <> i18nDirectory <> "’ has no translations for ‘" <> unpack kind <> "’") return . NonEmpty.nonEmpty
|
||||
|
||||
i18nWidgetFilesAvailable :: FilePath -> Q Exp
|
||||
i18nWidgetFilesAvailable = TH.lift <=< i18nWidgetFilesAvailable'
|
||||
|
||||
i18nWidgetFiles :: FilePath -> Q Exp
|
||||
i18nWidgetFiles basename = do
|
||||
availableTranslations' <- i18nWidgetFilesAvailable' basename
|
||||
|
||||
-- Dispatch to correct language (depending on user settings via `selectLanguage`) at run time
|
||||
ws <- newName "ws" -- Name for dispatch function
|
||||
letE
|
||||
[ funD ws $ [ clause [litP $ stringL kind, litP $ stringL l] (normalB [e|$(widgetFile $ "i18n" </> basename </> kind <.> l) :: Widget|]) []
|
||||
| (unpack -> kind, ls) <- Map.toList availableTranslations'
|
||||
, l <- unpack <$> NonEmpty.toList ls
|
||||
] ++ [ clause [wildP, wildP] (normalB [e| error "selectLanguage returned an invalid translation" |]) [] ] -- Fallback mostly there so compiler does not complain about non-exhaustive pattern match
|
||||
] [e|imap (\kind ls -> selectLanguage ls >>= $(varE ws) kind) availableTranslations'|]
|
||||
|
||||
@ -47,14 +47,14 @@ userMailT :: ( MonadHandler m
|
||||
) => UserId -> MailT m a -> m a
|
||||
userMailT uid mAct = do
|
||||
user@User
|
||||
{ userMailLanguages
|
||||
{ userLanguages
|
||||
, userDateTimeFormat
|
||||
, userDateFormat
|
||||
, userTimeFormat
|
||||
} <- liftHandler . runDB $ getJust uid
|
||||
let
|
||||
ctx = MailContext
|
||||
{ mcLanguages = userMailLanguages
|
||||
{ mcLanguages = fromMaybe def userLanguages
|
||||
, mcDateTimeFormat = \case
|
||||
SelFormatDateTime -> userDateTimeFormat
|
||||
SelFormatDate -> userDateFormat
|
||||
|
||||
@ -203,7 +203,7 @@ fltrAllocationActive cTime queryAllocation = singletonMap "active" . FilterColum
|
||||
E.&&. E.maybe E.true (\t -> E.val cTime E.<=. t) (allocation E.^. AllocationRegisterTo)
|
||||
|
||||
fltrAllocationActiveUI :: DBFilterUI
|
||||
fltrAllocationActiveUI mPrev = prismAForm (singletonFilter "active" . maybePrism _PathPiece) mPrev $ aopt boolField (fslI MsgAllocationActive)
|
||||
fltrAllocationActiveUI mPrev = prismAForm (singletonFilter "active" . maybePrism _PathPiece) mPrev $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgAllocationActive)
|
||||
|
||||
|
||||
-----------
|
||||
@ -355,7 +355,7 @@ fltrApplicationVeto :: OpticFilterColumn t Bool
|
||||
fltrApplicationVeto queryVeto = singletonMap "veto" . FilterColumn . mkExactFilter $ view queryVeto
|
||||
|
||||
fltrApplicationVetoUI :: DBFilterUI
|
||||
fltrApplicationVetoUI mPrev = prismAForm (singletonFilter "veto" . maybePrism _PathPiece) mPrev $ aopt boolField (fslI MsgCourseApplicationVeto)
|
||||
fltrApplicationVetoUI mPrev = prismAForm (singletonFilter "veto" . maybePrism _PathPiece) mPrev $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgCourseApplicationVeto)
|
||||
|
||||
colApplicationRatingComment :: OpticColonnade (Maybe Text)
|
||||
colApplicationRatingComment resultComment = Colonnade.singleton (fromSortable header) body
|
||||
@ -407,7 +407,7 @@ fltrApplicationFiles :: OpticFilterColumn t Bool
|
||||
fltrApplicationFiles queryFiles = singletonMap "has-files" . FilterColumn . mkExactFilter $ view queryFiles
|
||||
|
||||
fltrApplicationFilesUI :: DBFilterUI
|
||||
fltrApplicationFilesUI mPrev = prismAForm (singletonFilter "has-files" . maybePrism _PathPiece) mPrev $ aopt boolField (fslI MsgCourseApplicationFiles)
|
||||
fltrApplicationFilesUI mPrev = prismAForm (singletonFilter "has-files" . maybePrism _PathPiece) mPrev $ aopt (boolField . Just $ SomeMessage MsgBoolIrrelevant) (fslI MsgCourseApplicationFiles)
|
||||
|
||||
---------------
|
||||
-- Files
|
||||
|
||||
46
src/Mail.hs
46
src/Mail.hs
@ -10,7 +10,7 @@ module Mail
|
||||
, MailT, defMailT
|
||||
, MailSmtpData(..)
|
||||
, _MailSmtpDataSet
|
||||
, MailContext(..), MailLanguages(..)
|
||||
, MailContext(..)
|
||||
, MonadMail(..)
|
||||
, getMailMessageRender, getMailMsgRenderer
|
||||
-- * YesodMail
|
||||
@ -38,7 +38,7 @@ module Mail
|
||||
|
||||
import ClassyPrelude.Yesod hiding (snoc, (.=), getMessageRender, derivePersistFieldJSON)
|
||||
|
||||
import Model.Types.TH.JSON
|
||||
import Model.Types.Languages
|
||||
|
||||
import Network.Mail.Mime hiding (addPart, addAttachment)
|
||||
import qualified Network.Mail.Mime as Mime (addPart)
|
||||
@ -89,7 +89,7 @@ import qualified Data.Binary as Binary
|
||||
import "network-bsd" Network.BSD (getHostName)
|
||||
|
||||
import Data.Time.Zones (TZ, utcTZ, utcToLocalTimeTZ, timeZoneForUTCTime)
|
||||
import Data.Time.LocalTime (ZonedTime(..))
|
||||
import Data.Time.LocalTime (ZonedTime(..), TimeZone(..))
|
||||
import Data.Time.Format (rfc822DateFormat)
|
||||
|
||||
import Network.HaskellNet.SMTP (SMTPConnection)
|
||||
@ -109,8 +109,6 @@ import Data.Universe.Instances.Reverse ()
|
||||
import Data.Universe.Instances.Reverse.JSON ()
|
||||
import Data.Universe.Instances.Reverse.Hashable ()
|
||||
|
||||
import GHC.Exts (IsList)
|
||||
|
||||
import Control.Monad.Trans.Maybe (MaybeT(..))
|
||||
|
||||
import Data.CaseInsensitive (CI)
|
||||
@ -160,19 +158,8 @@ _MailSmtpDataSet = to $ \MailSmtpData{..} -> none id
|
||||
]
|
||||
|
||||
|
||||
newtype MailLanguages = MailLanguages { mailLanguages :: [Lang] }
|
||||
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
||||
deriving newtype (FromJSON, ToJSON, IsList)
|
||||
|
||||
instance Default MailLanguages where
|
||||
def = MailLanguages []
|
||||
|
||||
instance Hashable MailLanguages
|
||||
instance NFData MailLanguages
|
||||
|
||||
|
||||
data MailContext = MailContext
|
||||
{ mcLanguages :: MailLanguages
|
||||
{ mcLanguages :: Languages
|
||||
, mcDateTimeFormat :: SelDateTimeFormat -> DateTimeFormat
|
||||
} deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||
|
||||
@ -191,7 +178,7 @@ instance Default MailContext where
|
||||
makeLenses_ ''MailContext
|
||||
|
||||
class (MonadHandler m, MonadState Mail m) => MonadMail m where
|
||||
askMailLanguages :: m MailLanguages
|
||||
askMailLanguages :: m Languages
|
||||
askMailDateTimeFormat :: SelDateTimeFormat -> m DateTimeFormat
|
||||
tellMailSmtpData :: MailSmtpData -> m ()
|
||||
|
||||
@ -214,7 +201,7 @@ getMailMessageRender :: ( MonadMail m
|
||||
, HandlerSite m ~ site
|
||||
, RenderMessage site msg
|
||||
) => m (msg -> Text)
|
||||
getMailMessageRender = renderMessage <$> getYesod <*> (mailLanguages <$> askMailLanguages)
|
||||
getMailMessageRender = renderMessage <$> getYesod <*> (view _Wrapped <$> askMailLanguages)
|
||||
|
||||
getMailMsgRenderer :: forall site m.
|
||||
( MonadMail m
|
||||
@ -515,8 +502,24 @@ setDateCurrent = setDate =<< liftIO getCurrentTime
|
||||
setDate :: (MonadHandler m, YesodMail (HandlerSite m)) => UTCTime -> MailT m ()
|
||||
setDate time = do
|
||||
tz <- mailDateTZ
|
||||
let timeStr = formatTime defaultTimeLocale rfc822DateFormat $ ZonedTime (utcToLocalTimeTZ tz time) (timeZoneForUTCTime tz time)
|
||||
let timeStr = formatTime defaultTimeLocale rfc822DateFormat $ ZonedTime (utcToLocalTimeTZ tz time) (rfc822zone $ timeZoneForUTCTime tz time)
|
||||
replaceMailHeader "Date" . Just $ pack timeStr
|
||||
where
|
||||
rfc822zone tz'
|
||||
| tz' `elem` rfc822zones = tz'
|
||||
| otherwise = tz' { timeZoneName = "" }
|
||||
rfc822zones =
|
||||
[ TimeZone 0 False "UT"
|
||||
, TimeZone 0 False "GMT"
|
||||
, TimeZone (-5 * 60) False "EST"
|
||||
, TimeZone (-4 * 60) True "EDT"
|
||||
, TimeZone (-6 * 60) False "CST"
|
||||
, TimeZone (-5 * 60) True "CDT"
|
||||
, TimeZone (-7 * 60) False "MST"
|
||||
, TimeZone (-6 * 60) True "MDT"
|
||||
, TimeZone (-8 * 60) False "PST"
|
||||
, TimeZone (-7 * 60) True "PDT"
|
||||
]
|
||||
|
||||
|
||||
setMailSmtpData :: (MonadHandler m, YesodMail (HandlerSite m), MonadThrow m) => MailT m ()
|
||||
@ -543,6 +546,3 @@ setMailSmtpData = do
|
||||
in tell $ mempty { smtpEnvelopeFrom = Last $ Just verp }
|
||||
| otherwise
|
||||
-> tell $ mempty { smtpEnvelopeFrom = Last $ Just from }
|
||||
|
||||
|
||||
derivePersistFieldJSON ''MailLanguages
|
||||
|
||||
@ -577,6 +577,14 @@ customMigrations = Map.fromListWith (>>)
|
||||
ALTER TABLE "allocation" DROP COLUMN "matching_log";
|
||||
|]
|
||||
)
|
||||
, ( AppliedMigrationKey [migrationVersion|26.0.0|] [version|27.0.0|]
|
||||
, whenM (tableExists "user") $
|
||||
[executeQQ|
|
||||
ALTER TABLE "user" ADD COLUMN "languages" jsonb;
|
||||
UPDATE "user" SET "languages" = "mail_languages" where "mail_languages" <> '[]';
|
||||
ALTER TABLE "user" DROP COLUMN "mail_languages";
|
||||
|]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -14,3 +14,4 @@ import Model.Types.Submission as Types
|
||||
import Model.Types.Misc as Types
|
||||
import Model.Types.School as Types
|
||||
import Model.Types.Allocation as Types
|
||||
import Model.Types.Languages as Types
|
||||
|
||||
25
src/Model/Types/Languages.hs
Normal file
25
src/Model/Types/Languages.hs
Normal file
@ -0,0 +1,25 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving, UndecidableInstances #-}
|
||||
|
||||
module Model.Types.Languages
|
||||
( Languages(..)
|
||||
) where
|
||||
|
||||
import ClassyPrelude.Yesod hiding (derivePersistFieldJSON)
|
||||
import GHC.Exts (IsList)
|
||||
|
||||
import Model.Types.TH.JSON
|
||||
|
||||
import Control.Lens.TH (makeWrapped)
|
||||
|
||||
|
||||
newtype Languages = Languages [Lang]
|
||||
deriving (Eq, Ord, Show, Read, Generic, Typeable)
|
||||
deriving newtype (FromJSON, ToJSON, IsList)
|
||||
|
||||
instance Default Languages where
|
||||
def = Languages []
|
||||
|
||||
instance Hashable Languages
|
||||
instance NFData Languages
|
||||
derivePersistFieldJSON ''Languages
|
||||
makeWrapped ''Languages
|
||||
@ -19,6 +19,8 @@ import Data.Time (TimeLocale(..), NominalDiffTime, nominalDay)
|
||||
import Data.Time.Zones (TZ)
|
||||
import Data.Time.Zones.TH (includeSystemTZ)
|
||||
|
||||
import qualified Data.List.NonEmpty as NonEmpty
|
||||
|
||||
import Language.Haskell.TH
|
||||
import Language.Haskell.TH.Syntax (Lift(..))
|
||||
import Instances.TH.Lift ()
|
||||
@ -49,21 +51,18 @@ timeLocaleMap extra@((_, defLocale):_) = do
|
||||
localeMap <- newName "localeMap"
|
||||
|
||||
let
|
||||
localeMap' = funD localeMap $ map matchLang extra ++ [reduceLangList, defaultLang]
|
||||
langs = NonEmpty.fromList $ map fst extra
|
||||
|
||||
localeMap' = funD localeMap $ map matchLang extra ++ [defaultLang]
|
||||
|
||||
defaultLang :: ClauseQ
|
||||
defaultLang =
|
||||
clause [listP []] (normalB $ localeExp defLocale) []
|
||||
|
||||
reduceLangList :: ClauseQ
|
||||
reduceLangList = do
|
||||
ls <- newName "ls"
|
||||
clause [[p|(_ : $(varP ls))|]] (normalB [e|$(varE localeMap) $(varE ls)|]) []
|
||||
|
||||
matchLang :: (Lang, String) -> ClauseQ
|
||||
matchLang (lang, localeStr) = do
|
||||
lang' <- newName "lang"
|
||||
clause [[p|($(varP lang') : _)|]] (guardedB [(,) <$> normalG [e|$(varE lang') == lang|] <*> localeExp localeStr]) []
|
||||
clause [varP lang'] (guardedB [(,) <$> normalG [e|selectLanguage' langs $(varE lang') == lang|] <*> localeExp localeStr]) []
|
||||
|
||||
localeExp :: String -> ExpQ
|
||||
localeExp = lift <=< runIO . getLocale . Just
|
||||
|
||||
@ -228,6 +228,7 @@ data FormIdentifier
|
||||
| FIDAssignSubmissions
|
||||
| FIDUserAuthMode
|
||||
| FIDAllUsersAction
|
||||
| FIDLanguage
|
||||
deriving (Eq, Ord, Read, Show)
|
||||
|
||||
instance PathPiece FormIdentifier where
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
module Utils.Frontend.I18n
|
||||
( FrontendMessage(..)
|
||||
, frontendDatetimeLocales
|
||||
) where
|
||||
|
||||
import ClassyPrelude
|
||||
@ -14,6 +15,9 @@ import Data.Aeson.Types (toJSONKeyText)
|
||||
import Data.Aeson.TH
|
||||
import qualified Data.Char as Char
|
||||
|
||||
import Data.List.NonEmpty (NonEmpty(..))
|
||||
import Text.Shakespeare.I18N (Lang)
|
||||
|
||||
|
||||
-- | I18n-Messages used in JavaScript-Frontend
|
||||
--
|
||||
@ -39,3 +43,7 @@ instance ToJSONKey FrontendMessage where
|
||||
toJSONKey = toJSONKeyText toPathPiece
|
||||
instance FromJSONKey FrontendMessage where
|
||||
fromJSONKey = FromJSONKeyTextParser $ parseJSON . String
|
||||
|
||||
|
||||
frontendDatetimeLocales :: NonEmpty Lang
|
||||
frontendDatetimeLocales = "de" :| ["en"]
|
||||
|
||||
@ -3,9 +3,19 @@ module Utils.Lang where
|
||||
import ClassyPrelude.Yesod
|
||||
|
||||
import qualified Data.List.NonEmpty as NonEmpty
|
||||
import Data.List.NonEmpty (NonEmpty(..))
|
||||
import Data.List.NonEmpty (NonEmpty(..), nonEmpty)
|
||||
|
||||
import qualified Data.Text as Text
|
||||
import qualified Data.Text.Encoding as Text
|
||||
import qualified Data.List as List
|
||||
|
||||
import qualified Data.CaseInsensitive as CI
|
||||
|
||||
import Yesod.Core.Types (HandlerData(handlerRequest), YesodRequest(reqLangs))
|
||||
import qualified Network.Wai.Parse as NWP
|
||||
|
||||
import Control.Monad.Trans.Maybe (MaybeT(..))
|
||||
import Control.Monad.Reader.Class (local)
|
||||
|
||||
|
||||
selectLanguage :: MonadHandler m
|
||||
@ -16,18 +26,44 @@ selectLanguage avL = selectLanguage' avL <$> languages
|
||||
selectLanguage' :: NonEmpty Lang -- ^ Available translations, first is default
|
||||
-> [Lang] -- ^ Languages in preference order
|
||||
-> Lang
|
||||
selectLanguage' (defL :| _) [] = defL
|
||||
selectLanguage' avL (l:ls)
|
||||
selectLanguage' avL ls = NonEmpty.head $ selectLanguages avL ls
|
||||
|
||||
selectLanguages :: NonEmpty Lang -> [Lang] -> NonEmpty Lang
|
||||
selectLanguages (defL :| _) [] = defL :| []
|
||||
selectLanguages avL (l:ls)
|
||||
| not $ null l
|
||||
, Just l' <- find (== l) (NonEmpty.toList avL)
|
||||
= l'
|
||||
| not $ null l
|
||||
, Just lParts <- NonEmpty.nonEmpty $ Text.splitOn "-" l
|
||||
, found <- find ((NonEmpty.toList lParts `isPrefixOf`) . Text.splitOn "-") avL
|
||||
= flip fromMaybe found $ selectLanguage' avL $ Text.intercalate "-" (NonEmpty.tail lParts) : ls
|
||||
| otherwise = selectLanguage' avL ls
|
||||
, Just lParts <- nonEmpty $ matchesFor l
|
||||
, found <- List.nub
|
||||
[ l'' | lParts' <- NonEmpty.toList lParts
|
||||
, l' <- NonEmpty.toList avL
|
||||
, l'' <- matchesFor l'
|
||||
, langMatches lParts' l''
|
||||
]
|
||||
= let now = nonEmpty $ sortOn (Down . length) found
|
||||
others = selectLanguages avL ls
|
||||
in maybe id (\now' others' -> NonEmpty.fromList $ toList now' ++ filter (`notElem` toList now') (toList others')) now others
|
||||
| otherwise = selectLanguages avL ls
|
||||
|
||||
langMatches :: Lang -- ^ Needle
|
||||
-> Lang -- ^ Haystack
|
||||
-> Bool
|
||||
langMatches = isPrefixOf `on` Text.splitOn "-"
|
||||
langMatches (CI.foldCase -> needle) (CI.foldCase -> haystack) = needle `elem` matchesFor haystack
|
||||
|
||||
matchesFor :: Lang -> [Lang]
|
||||
matchesFor = mapMaybe (\frags -> Text.intercalate "-" frags <$ guard (not $ null frags)) . reverse . List.inits . Text.splitOn "-"
|
||||
|
||||
|
||||
highPrioRequestedLangs, lowPrioRequestedLangs :: forall m. MonadHandler m => m [Lang]
|
||||
highPrioRequestedLangs = fmap (concatMap $ fromMaybe []) . mapM runMaybeT $
|
||||
[ lookupGetParams "_LANG"
|
||||
, lookupCookies "_LANG"
|
||||
, fmap pure . MaybeT $ lookupSession "_LANG"
|
||||
]
|
||||
lowPrioRequestedLangs = maybe [] (mapMaybe (either (const Nothing) Just . Text.decodeUtf8') . NWP.parseHttpAccept) <$> lookupHeader "Accept-Language"
|
||||
|
||||
languagesMiddleware :: forall site a. NonEmpty Lang -> HandlerFor site a -> HandlerFor site a
|
||||
languagesMiddleware avL act = do
|
||||
pLangs <- fmap List.nub $ (<>) <$> highPrioRequestedLangs <*> lowPrioRequestedLangs
|
||||
let langs = toList $ selectLanguages avL pLangs
|
||||
setLangs hData = hData{ handlerRequest = (handlerRequest hData){ reqLangs = langs } }
|
||||
local setLangs $ ($logDebugS "languages" . tshow . (pLangs,langs,) =<< languages) *> act
|
||||
|
||||
@ -56,6 +56,7 @@ data GlobalPostParam = PostFormIdentifier
|
||||
| PostMassInputShape
|
||||
| PostBearer
|
||||
| PostDBCsvImportAction
|
||||
| PostLoginDummy
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
||||
|
||||
instance Universe GlobalPostParam
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
$newline never
|
||||
<section>
|
||||
^{degreeTable}
|
||||
<section>
|
||||
^{studytermsTable}
|
||||
<section>
|
||||
<h2>_{MsgStudyFeatureInference}
|
||||
<h2>_{MsgStudyFeatureInference}
|
||||
$if null infConflicts
|
||||
<p>
|
||||
$if null infConflicts
|
||||
Kein Konflikte beobachtet.
|
||||
$else
|
||||
<h3>Studiengangseingträge mit beobachteten Konflikten:
|
||||
<ul>
|
||||
$forall (Entity _ (StudyTerms ky _ nm)) <- infConflicts
|
||||
<li> #{show ky} - #{foldMap id nm}
|
||||
^{btnForm}
|
||||
_{MsgStudyFeatureInferenceNoConflicts}
|
||||
$else
|
||||
<h3>_{MsgStudyFeatureInferenceConflictsHeading}
|
||||
<ul>
|
||||
$forall (Entity _ (StudyTerms ky _ nm)) <- infConflicts
|
||||
<li> #{show ky} - #{foldMap id nm}
|
||||
^{btnForm}
|
||||
|
||||
<div .container>
|
||||
^{candidateTable}
|
||||
<div .container>
|
||||
^{candidateTable}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
$newline never
|
||||
<section>
|
||||
^{userDataWidget}
|
||||
<section>
|
||||
@ -10,9 +11,6 @@
|
||||
^{authForm}
|
||||
<section>
|
||||
<p>
|
||||
Achtung, dieser Link löscht momentan noch den kompletten Benutzer
|
||||
unwiderruflich aus der Live-Datenbank mit
|
||||
<code>DELETE CASCADE uid
|
||||
\ Prüfungs- und Klausurdaten müssen jedoch langfristig gespeichert werden!
|
||||
_{MsgUserAccountDeleteWarning}
|
||||
<p>
|
||||
^{modal "Benutzer löschen" (Right deleteWidget)}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
$newline never
|
||||
<div .container>
|
||||
<h1>
|
||||
_{MsgUserAccountDeleted userDisplayName}
|
||||
@ -6,13 +7,10 @@
|
||||
<div .container>
|
||||
#{mailtoHtml userEmail}
|
||||
<div .container>
|
||||
#{deletedSubmissions} Abgaben wurden unwiederruflich gelöscht.
|
||||
_{MsgUserSubmissionsDeleted deletedSubmissions}
|
||||
$if groupSubmissions > 0
|
||||
<div .container>
|
||||
#{groupSubmissions} Gruppenabgaben verbleiben in der Datenbank,
|
||||
aber die Zuordnung zum Benutzer wurden gelöscht.
|
||||
Gruppenabgaben können dadurch zu Einzelabgaben werden,
|
||||
welche dann vom letzten Benutzer gelöscht werden können.
|
||||
_{MsgUserGroupSubmissionsKept groupSubmissions}
|
||||
$if deletedSubmissionGroups > 0
|
||||
<div .container>
|
||||
#{deletedSubmissionGroups} benannte Abgabengruppen wurden gelöscht, da diese dadurch leer wurden.
|
||||
_{MsgUserSubmissionGroupsDeleted deletedSubmissionGroups}
|
||||
|
||||
14
templates/glossary.cassius
Normal file
14
templates/glossary.cassius
Normal file
@ -0,0 +1,14 @@
|
||||
.glossary
|
||||
dt, .dt
|
||||
font-weight: 600
|
||||
|
||||
&.sec
|
||||
font-style: italic
|
||||
font-size: 0.9rem
|
||||
font-weight: 600
|
||||
color: var(--color-fontsec)
|
||||
dd, .dd
|
||||
margin-left: 12px
|
||||
|
||||
dd + dt, .dd + dt, dd + .dt, .dd + .dt
|
||||
margin-top: 17px
|
||||
5
templates/glossary.hamlet
Normal file
5
templates/glossary.hamlet
Normal file
@ -0,0 +1,5 @@
|
||||
$newline never
|
||||
<dl .glossary>
|
||||
$forall (term, rTerm, wgt) <- entries'
|
||||
<dt ##{term}>#{rTerm}
|
||||
^{wgt}
|
||||
@ -1,6 +1,6 @@
|
||||
if (window.App) {
|
||||
window.App.i18n.addMany(#{frontendI18n});
|
||||
// window.App.i18n.setLang(lang); TODO: set language string for datepicker config
|
||||
window.App.i18n.setDatetimeLocale(#{frontendDatetimeLocale});
|
||||
} else {
|
||||
throw new Error('I18n JavaScript service is missing!');
|
||||
}
|
||||
|
||||
65
templates/i18n/admin-test/en-eu.hamlet
Normal file
65
templates/i18n/admin-test/en-eu.hamlet
Normal file
@ -0,0 +1,65 @@
|
||||
<section>
|
||||
|
||||
|
||||
<p>
|
||||
This page is intended only to test certain functionality and to demonstrate
|
||||
various helper functions and modules.
|
||||
|
||||
This handler should contain up to date examples of all functions so it can
|
||||
be the first point of contact.
|
||||
|
||||
<p>
|
||||
^{iconTooltip testTooltipMsg Nothing False}
|
||||
^{messageTooltip msgInfoTooltip}
|
||||
^{messageTooltip msgSuccessTooltip}
|
||||
^{messageTooltip msgWarningTooltip}
|
||||
^{messageTooltip msgErrorTooltip}
|
||||
^{messageTooltip msgNonDefaultIconTooltip}
|
||||
|
||||
<p>
|
||||
<span .tooltip>
|
||||
<span .tooltip__content>Buy Tooltip-White for brighter tooltips!
|
||||
The tooltip handle should always be set so tooltips are usable on mobile devices and without javascript.
|
||||
|
||||
<section>
|
||||
<h2>Manually created table
|
||||
<table .table .table--striped>
|
||||
<tr .table__row .table__row--head>
|
||||
<th .table__th colspan=2> Header A
|
||||
<th .table__th rowspan=2> Header
|
||||
<tr .table__row .table__row--head>
|
||||
<th .table__th> B
|
||||
<th .table__th> C
|
||||
<tr .table__row title="Example of line tooltip">
|
||||
<td .table__td>1
|
||||
<td .table__td>2
|
||||
<td .table__td>3
|
||||
<tr .table__row>
|
||||
<td .table__td>
|
||||
<span .tooltip>
|
||||
<span .tooltip__content>Buy Tooltip-White for brighter tooltips! (DOES NOT WORK)
|
||||
4
|
||||
<td .table__td>5
|
||||
<span .tooltip>
|
||||
<span .tooltip__content>Buy Tooltip-White for brighter tooltips! (DOES NOT WORK)
|
||||
<td .table__td>6
|
||||
<tr .table__row>
|
||||
<td .table__td>7
|
||||
<td .table__td>8
|
||||
<td .table__td>9
|
||||
|
||||
<section>
|
||||
<h2>Functions for testing
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Button test:
|
||||
^{btnForm}
|
||||
<li>
|
||||
Modals:
|
||||
<ul>
|
||||
<li>^{modal "Click me for ajax test" (Left $ SomeRoute UsersR)}
|
||||
<li>^{modal "Click me for content test" (Right "Test Inhalt für Modal")}
|
||||
<li>^{modal "Email-Test" (Right emailWidget')}
|
||||
<li>
|
||||
Some icons: ^{isVisible False} ^{hasComment True}
|
||||
88
templates/i18n/allocation-info/en-eu.hamlet
Normal file
88
templates/i18n/allocation-info/en-eu.hamlet
Normal file
@ -0,0 +1,88 @@
|
||||
$newline text
|
||||
<section>
|
||||
<p>
|
||||
Every central allocations progresses through the following stages in order:
|
||||
<dl .deflist>
|
||||
<dt .deflist__dt>
|
||||
_{MsgAllocationStaffRegister}
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Course administrators may register their courses for participation in
|
||||
the central allocation and deregister them again at will.
|
||||
<p>
|
||||
Whether students are expected to provide text or files with their
|
||||
applications is configured on a course-by-course basis.
|
||||
Course administrators may specify instructions for application e.g. what
|
||||
content the application files should have.
|
||||
<p>
|
||||
Courses registered for participation in the central allocation can not
|
||||
gain participants through the entire course of the allocation.
|
||||
Not even through manual enrollment by course administrators.
|
||||
|
||||
<dt .deflist__dt>
|
||||
_{MsgAllocationRegister}
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Only during this phase may students apply to the various courses which
|
||||
participate in the central allocation.
|
||||
<p>
|
||||
Applicants may assign priorities ("this course would be my first choice"
|
||||
down to "I will only participate in this course if nothing else is
|
||||
available to me") to courses they apply to.
|
||||
Multiple courses may have the same priority.
|
||||
<p>
|
||||
Applications and priorities may be freely edited and retracted during
|
||||
the application period.
|
||||
<p>
|
||||
Students may request more than one placement from a central allocation.
|
||||
|
||||
Additional placements are made if sufficient capacity is available
|
||||
and/or placements are sufficiently urgent.
|
||||
<p>
|
||||
Application texts and/or files need to be specified for each course
|
||||
individually, as requested by the course administrators.
|
||||
|
||||
<dt .deflist__dt>
|
||||
_{MsgAllocationStaffAllocation}
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Only during this phase may course administrators inspect and grade
|
||||
applications.
|
||||
$# <p>
|
||||
$# Nur in manchen Zentralanmeldungen dürfen Veranstalter
|
||||
$# Bewerber jetzt direkt ablehnen und/oder übernehmen.
|
||||
$# <p>
|
||||
$# Veranstalter haben noch eine letzte Möglichkeit,
|
||||
$# die Kurskapazität anzupassen.
|
||||
|
||||
<dt .deflist__dt>
|
||||
_{MsgAllocationProcess}
|
||||
<dd .deflist__dd>
|
||||
<p>
|
||||
Placements in courses are made with regard to the current study
|
||||
progress, urgency, and the grading of the application as determined by a
|
||||
course administrator.
|
||||
<p>
|
||||
Applicants are directly enrolled in the selected courses.
|
||||
|
||||
Applicants which leave courses they were assigned in the course of a
|
||||
central allocation may be affected negatively by this in future central
|
||||
allocations.
|
||||
<p>
|
||||
Only after placements have been made, may course administrators enroll
|
||||
and deregister participants of their courses.
|
||||
Course administrators can request to be assigned successors to fill
|
||||
their courses' remaining capacity.
|
||||
|
||||
<p>
|
||||
Central allocation procedure may vary.
|
||||
|
||||
<em>
|
||||
In particular: #
|
||||
|
||||
If, on the central allocation's page, no times are specified for one of the
|
||||
phases mentioned above, the time in question has not been determined yet!
|
||||
<p>
|
||||
Multiple central allocations are handled entirely independently of one
|
||||
another.
|
||||
|
||||
253
templates/i18n/changelog/en-eu.hamlet
Normal file
253
templates/i18n/changelog/en-eu.hamlet
Normal file
@ -0,0 +1,253 @@
|
||||
$newline never
|
||||
<dl .deflist>
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 10 23}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Introduced option to timestamp all exported CSV files
|
||||
<li>Introduced CSV export option to set the character encoding used
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 10 14}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Control of settings for a tutorial may be delegated to the respective tutors
|
||||
<li>Optionally display sex in (among others) lists of course participants
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 10 10}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>CSV export of course participants now includes registered tutorials
|
||||
<li>Course participant may be registered for tutorials via the course participant table
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 10 09}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Course occurrences
|
||||
<li>CSV export of course participants now optionally includes all features of study
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 10 08}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Users are notified if they are enrolled in courses by administrators
|
||||
<li>CSV export of course participants
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 10 01}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Course news
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 09 27}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Option to automatically accept applications for courses outside of central allocations
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 09 25}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Automatic computation of exam boni
|
||||
<li>Automatic computation of exam results
|
||||
<li><i>Bugfix</i>: Times are no longer reset when loading a form
|
||||
<li><i>Bugfix</i>: Participants are no longer duplicated in the exam achievements table
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 09 16}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Exam offices (including direct access to relevant exam achievements)
|
||||
<li>Email notifications to relevant exam offices when exams are closed
|
||||
<li>Closure of exams (i.e. notification of relevant exam offices) is now a button instead of a predetermined time
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 09 13}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Notifications for exam registration and deregistration
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 09 12}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Estimation of course capacity required to satisfy central allocations
|
||||
<li>Configurable display names
|
||||
<li>Configulable display emails
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 09 05}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Notifications for central allocations
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 08 27}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Grading of course applications via CSV
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 08 19}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Applications for central allocations
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 08 12}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Registration of courses for central allocation
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 07 23}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>CSV import & export of exam participants
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 06 26}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Rudimentary support for exams
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 06 07}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Exercise sheets can enforce certain file names and extensions
|
||||
<li>Download of all files for exercise sheets (by classification) as ZIP archives
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 05 20}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Completely reworked automatic distribution of corrections
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 05 13}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Course administrators may enroll participants
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 05 10}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Improved interface for configuring submittors
|
||||
<li>Download of all files for course material/exercise sheets
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 05 04}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Course material
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 04 29}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Tutorials
|
||||
<li>Display of correctors on course overview pages
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 04 20}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Sending of course messages to participants
|
||||
<li>Configuration of course correctors and administrators without existing accounts
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 03 27}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Course administrators can now configure course administrators and assistants themselves
|
||||
<li>Features of study
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 03 20}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Course enrollment requires association of a field of study (for students with multiple fields)
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 01 30}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Design changes
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2019 01 16}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Convenience links (i.e. current exercise sheet)
|
||||
<li>Filters for list of assigned corrections
|
||||
<li><i>Bugfix</i>: Proper error message for if entries vanish between generation of table form and submission
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2018 11 30}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li><i>Bugfix</i>: Exercise sheets in "passing by points"-mode now saved correctly again
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2018 11 29}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li><i>Bugfix</i>: Table forms now work after JavaScript page changes and changes in sorting
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2018 11 09}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li><i>Bugfix</i>: Multiple buttons/forms no work again when JavaScript is enabled
|
||||
<li>Multiple improvements for correctors
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2018 10 19}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>During testing users may completely delete their accounts
|
||||
<li>Support widget
|
||||
<li>Email notifications for some events
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2018 09 18}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Tooltips now work without JavaScript
|
||||
<li>Course shorthands now only need to be unique within a department
|
||||
<li>Personal information now shows all currently saved data
|
||||
<li>Support for table summaries e.g. sums of exercise points
|
||||
<li>Smart distribution of corrections among correctors (e.g. when some are sick)
|
||||
<li>Exercise sheets may prohibit submission of files and determine whether ZIP archives should be unpacked automatically
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2018 08 06}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Option whether files should be downloaded automatically
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2018 08 01}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Improved campus login<br />
|
||||
(Replacement of a C-library with undocumented runtime dependencies with a new haskell-library now supports special characters)
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2018 07 31}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Numerous improvements for display of corrections
|
||||
<li>Overall course list for all semesters (see "Courses"), will have filters and search functions in the future
|
||||
|
||||
<dt .deflist__dt>
|
||||
^{formatGregorianW 2018 07 10}
|
||||
<dd .deflist__dd>
|
||||
<ul>
|
||||
<li>Bugfixes
|
||||
<li>Configurable date and time formats
|
||||
27
templates/i18n/corrections-upload-instructions/en-eu.hamlet
Normal file
27
templates/i18n/corrections-upload-instructions/en-eu.hamlet
Normal file
@ -0,0 +1,27 @@
|
||||
<section>
|
||||
<p>
|
||||
Uploading a correction automatically marks it finished (i.e. visible to the
|
||||
students) iff you were assigned that correction.
|
||||
<p>
|
||||
If an administrator that was not assigned the correction uploads it, it will
|
||||
not be marked finished.
|
||||
<p>
|
||||
It is expected that rating files will, in the future, contain a field to
|
||||
mark the correction as finished or not.
|
||||
<p>
|
||||
Contrary to UniWorX downloaded submissions always reflect the current state
|
||||
of the correction.
|
||||
This extends to files changed during previous corrections.
|
||||
|
||||
<section>
|
||||
<p>
|
||||
While marking a correction files may be changed, added, and deleted.
|
||||
These changes will be visible to the submittors as soon as the correction is
|
||||
marked finished.
|
||||
<p>
|
||||
Temporary files left by previous corrections should thus be deleted by the
|
||||
last corrector if they should not be accessable to submittors.
|
||||
|
||||
$maybe maxUpload <- maxUploadMB
|
||||
<p>
|
||||
Uploads are currently limited to #{textBytes maxUpload}
|
||||
25
templates/i18n/course-exam-office-explanation/en-eu.hamlet
Normal file
25
templates/i18n/course-exam-office-explanation/en-eu.hamlet
Normal file
@ -0,0 +1,25 @@
|
||||
$newline never
|
||||
<p>
|
||||
Here you can opt out of your exam achievements being shared with certain exam #
|
||||
offices within Uni2work.
|
||||
<p>
|
||||
Please keep in mind that sharing your exam achievements with the relevant exam #
|
||||
offices directly in Uni2work drastically reduces the workload (and thus time) #
|
||||
required to properly consider all your exam achievements.
|
||||
<p>
|
||||
Some exam offices might have access to your exam achievements regardless of #
|
||||
your settings.<br />
|
||||
This is only the for well founded individual cases (e.g. students in the #
|
||||
ERASMUS-programme).
|
||||
<p>
|
||||
Users that have access to your exam achievements regardless of your settings #
|
||||
(e.g. course administrators) are, of course, free to forward your exam #
|
||||
achievements to exam offices that do not pull from Uni2work (in that case they #
|
||||
might not be listed here).
|
||||
$if hasForced
|
||||
<p>
|
||||
If you are unable to opt out of sharing your exam achievements with certain #
|
||||
exam offices, that exam office has specified that access to your grades is #
|
||||
required either because of one of your fields of study (e.g. because study #
|
||||
regulations) or for you specifically.
|
||||
|
||||
25
templates/i18n/data-delete/en-eu.hamlet
Normal file
25
templates/i18n/data-delete/en-eu.hamlet
Normal file
@ -0,0 +1,25 @@
|
||||
<h2>
|
||||
Are you sure that you want to permanently delete ^{nameEmailWidget userEmail userDisplayName userSurname}?
|
||||
<p>
|
||||
During the testing phase users are deleted wholly from the live database via
|
||||
<code>DELETE CASCADE uid
|
||||
.
|
||||
Nontheless exam achievements must be stored for 5 years after exmatriculation!
|
||||
<p>
|
||||
Users can log in again using their campus account at any time, which will
|
||||
result in their user account starting empty again.
|
||||
<p>
|
||||
Uploaded submission files are deleted if they were associated with the deleted
|
||||
user only.
|
||||
Files of group submissions are only deleted when their last group member is
|
||||
deleted.
|
||||
<p>
|
||||
<em>Caution:
|
||||
Exercise submissions are deleted as well!
|
||||
If a lecturer has not backed up the information regarding submissions
|
||||
elsewhere this is expected to result in a loss of exercise bonus.
|
||||
Grades synchronised externally should not be impacted by this but may not be
|
||||
able to be reconstructed without the information of Uni2work-managed
|
||||
submissions.
|
||||
|
||||
^{deleteForm}
|
||||
@ -146,7 +146,7 @@ $# <li>übertragene Datenmenge
|
||||
$# <li>Info, ob der Zugriff/Abruf erfolgreich war
|
||||
$# <li>Art und Version des Webclients
|
||||
$# <li>gegebenenfalls Fehlermeldungen
|
||||
$# <li>gegebenenfalls Text einer Suchanfrage
|
||||
$# <li>gegebenenfalls Text der Such-/Filteranfrage oder der Sortierparameter
|
||||
$# <p>
|
||||
$# Im Falle einer Störung oder eines Sicherheitsvorfalles wird für die Dauer des Vorfalles
|
||||
$# die Anonymisierung der IP-Adresse aufgehoben.
|
||||
196
templates/i18n/data-protection/en.hamlet
Normal file
196
templates/i18n/data-protection/en.hamlet
Normal file
@ -0,0 +1,196 @@
|
||||
$newline never
|
||||
<section>
|
||||
<h2>Last changed 19.02.2019
|
||||
<p>
|
||||
The LMU as a corporate body of public law is subject to #
|
||||
the BayDSG (bavarian legislation on data-protection), in some points the BDSG (Federal Data Protection Act), #
|
||||
the GDPR (General Data Protection Regulation), and the corresponding articles of special laws (Telemedia, Telecommunication, Employment Law, etc.) relevant to data protection.<br />
|
||||
This data privacy statement fulfills the obligations to inform the user as result from the formalities mentioned above.
|
||||
|
||||
|
||||
<section>
|
||||
<h2>Contact
|
||||
<h3>Data Protection Official of the LMU Munich
|
||||
<ul style="list-style-type: none">
|
||||
<li>Dr. Rolf Gemmeke
|
||||
<li>Geschwister-Scholl-Platz 1, 80539 München
|
||||
<li>Telefon: +49 (0) 89 2180-2414
|
||||
<li>
|
||||
<a href="http://www.uni-muenchen.de/einrichtungen/orga_lmu/beauftragte/dschutz/index.html">
|
||||
Website of the data protection official of the LMU
|
||||
|
||||
<h3>Supervisory body for data protection in the public sector
|
||||
<ul style="list-style-type: none">
|
||||
<li>Bayerischer Landesbeauftragter für den Datenschutz
|
||||
<li>Promenade 27
|
||||
<li>91522 Ansbach
|
||||
<li>Telefon: +49 (0) 981 53 1300
|
||||
<li>
|
||||
<a href="http://www.datenschutz-bayern.de/">
|
||||
Website of the bavarian Data Protection Commissioner
|
||||
|
||||
<h3> Data Protection Coordinator of the department Institut für Informatik of the LMU
|
||||
<ul style="list-style-type: none">
|
||||
<li>Robert Hofer
|
||||
<li>E-Mail: ^{mailtoHtml "dsk@ifi.lmu.de"}
|
||||
<li>Telefon: +49 (0) 89 / 2180 - 9198
|
||||
|
||||
<h3>Legal person responsible for data processing
|
||||
<ul style="list-style-type: none">
|
||||
<li>Ludwig-Maximilians-Universität München
|
||||
<li>Geschwister-Scholl-Platz 1
|
||||
<li>80539 München
|
||||
<li>Telefon: +49 (0) 89 / 2180 - 0
|
||||
<li>E-Mail: ^{mailtoHtml "praesidium@lmu.de"}
|
||||
<li>
|
||||
<p>
|
||||
The LMU Munich is a corporate body of the public law.<br />
|
||||
It is legally represented by its president, Prof. Dr. Bernd Huber.
|
||||
|
||||
<h4>Responsible department
|
||||
<ul style="list-style-type: none">
|
||||
<li> IT Operations Team (RBG) of the department Institut für Informatik of the LMU Munich
|
||||
<li>Oettingenstraße 67
|
||||
<li>D-80538 München
|
||||
<li>E-Mail: ^{mailtoHtml "rbg@ifi.lmu.de"}
|
||||
<li>Tel.: +49 (0) 89 / 2180 - 9198
|
||||
|
||||
<section>
|
||||
<h2>Processing of Personal Data
|
||||
<p>
|
||||
The IT service and organization at the department Institut für Informatik follows #
|
||||
the state of the art and best practices regarding security and IT operations.<br />
|
||||
The protection of personal data as well as the sustainable operation of services #
|
||||
in the scope of their possibilities is therefore guaranteed.
|
||||
|
||||
<h3>1. Webserver protocol
|
||||
|
||||
<h4>Data subjects
|
||||
|
||||
Every user of this webserver is affected by the acquisition and processing of the data.
|
||||
|
||||
<h4>Which data is acquired
|
||||
|
||||
The webserver records
|
||||
<ul>
|
||||
<li> Pseudonym corresponding to the IP address of the user's webclient
|
||||
<li> Date and time of the request of an element of the website
|
||||
<li> Address of the requested element
|
||||
<li> Amount of requested data
|
||||
<li> Info on whether the request was successful
|
||||
<li> Type and version of the webclient
|
||||
<li> Error messages if applicable
|
||||
<li> Text of any search, filter, or sorting parameter as applicable
|
||||
<p>
|
||||
In case of a disturbance or security incident, the pseudonymisation of the IP address will be #
|
||||
temporarily suspended.
|
||||
<p>
|
||||
ipscrub (<a href="http://www.ipscrub.org">http://www.ipscrub.org</a>) is used to generate pseudonyms for IP addresses.
|
||||
|
||||
|
||||
<h4>Appropriation
|
||||
<p>
|
||||
The collected data is only used for statistical analysis (in anonymised form), for enhancing Uni2work, #
|
||||
for analysis, elimination and protection against disturbances, and in case of security incidents.<br />
|
||||
Only the IT administrators responsible for the operation of the department Institut für Informatik #
|
||||
have access to the data.
|
||||
|
||||
<h4> Legal or Contractual Basis of Data Processing
|
||||
<ul>
|
||||
<li> Obligation to sustainable and secure operation of IT services according to the state of the art (TMG, TKG, DSG, EUDGV, BayDSG, BDSG)
|
||||
<li> Legislation pertaining to the retention period and type of webserver protocols
|
||||
<li> Performing of a function that is of public interest
|
||||
|
||||
<h4> Disclosure
|
||||
<p> First point of contact is the above-mentioned responsible department.
|
||||
|
||||
<h4> Deletion
|
||||
<p>
|
||||
Entries of the webserver protocol will be automatically deleted after seven days.<br />
|
||||
Data that is processed due to a disturbance or a security incident will be deleted after the incident
|
||||
has been concluded.
|
||||
|
||||
<h4> Consent, Correction, Revocation, Request for Deletion or Transmission
|
||||
<p>
|
||||
Consent is not required for the processing of the data due to the type of the collected data, its designated use, #
|
||||
the automated deletion and the basis of the collection (GDPR, Art. 6 Para. 1 e+f).<br />
|
||||
The right of withdrawal for data processing, the right of petition for deletion, the right of petition #
|
||||
for correction, and the right of petition for transmission are not applicable due to the consent to data processing #
|
||||
not being necessary as well as the type and use of the collected data.
|
||||
|
||||
<h4> Right to appeal
|
||||
<p>
|
||||
Users generally have the right to appeal to the supervisory body concerning any processing or transmission #
|
||||
of their personal data.<br />
|
||||
In case of the LMU Munich, the supervisory body is the above-mentioned Bavarian Data Protection Commissioner.<br />
|
||||
Apart from that, any other above-mentioned legal contact person may be contacted concerning appeals and inquiries.
|
||||
|
||||
<h4> Obligation to participate in the processing of the data
|
||||
<p>
|
||||
The user is obligated to provide the data and allow its processing when using this service.<br />
|
||||
We reserve the right to exclude users from the service who do not provide the data.
|
||||
|
||||
$# <!-- CONTINUED -->
|
||||
$#
|
||||
$# <h3>1. Daten der Webapplikation "Uni2Work"
|
||||
$#
|
||||
$# <h4>Betroffene
|
||||
$# Jeder Nutzer dieses Webservers ist von der Erhebung und Verarbeitung der Daten betroffen.
|
||||
$#
|
||||
$# <h4>Welche Daten werden erhoben
|
||||
$# Der Webserver protokolliert
|
||||
$# <ul>
|
||||
$# <li>Pseudonymisierte IP-Adresse des Webclients des Nutzers dieses Dienstes
|
||||
$# <li>Datum und Uhrzeit des Abrufs eines Elementes der Webseite
|
||||
$# <li>Adresse des abgerufenen Elementes
|
||||
$# <li>übertragene Datenmenge
|
||||
$# <li>Info, ob der Zugriff/Abruf erfolgreich war
|
||||
$# <li>Art und Version des Webclients
|
||||
$# <li>gegebenenfalls Fehlermeldungen
|
||||
$# <li>gegebenenfalls Text einer Suchanfrage
|
||||
$# <p>
|
||||
$# Im Falle einer Störung oder eines Sicherheitsvorfalles wird für die Dauer des Vorfalles
|
||||
$# die Anonymisierung der IP-Adresse aufgehoben.
|
||||
$#
|
||||
$# <h4>Zweckbindung
|
||||
$# <p>
|
||||
$# Die erhobenen Daten werden nur für statistische Zwecke(anonymisiert), zur Verbesserung des Angebots,
|
||||
$# zur Analyse, Beseitigung und Abwehr von Störungen und bei Sicherheitsvorfällen verwendet.
|
||||
$# Nur die für den Betrieb zuständigen IT Administratoren des Instituts für Informatik
|
||||
$# haben Zugriff auf die Daten.
|
||||
$#
|
||||
$# <h4>Rechtliche oder vertragliche Grundlagen der Datenverarbeitung
|
||||
$# <ul>
|
||||
$# <li>Verpflichtung zum nachhaltigen und sicheren Betrieb von IT Diensten nach Stand der Technik (TMG, TKG, DSG, EUDGV, BayrDSG, BDSG)
|
||||
$# <li>Rechtsprechung zur Aufbewahrungsdauer und Art von Webserverprotokollen
|
||||
$# <li>Wahrnehmung einer Aufgabe, die im öffentlichen Interesse liegt
|
||||
$#
|
||||
$# <h4>Auskunft
|
||||
$# <p>Erster Ansprechspartner ist der oben aufgeführte verantwortliche Fachbereich.
|
||||
$#
|
||||
$# <h4>Löschung
|
||||
$# <p>
|
||||
$# Nach sieben Tagen werden Einträge des Webserverprotokolls automatisch gelöscht. Daten die wegen einer
|
||||
$# Störung oder eines Sicherheitsvorfalles verarbeitet werden, werden nach Ende des Vorfalls gelöscht.
|
||||
$#
|
||||
$# <h4>Zustimmung, Berichtigung, Wiederruf, Antrag auf Löschung oder Übertragung
|
||||
$# <p>
|
||||
$# Eine Zustimmung zur Datenverarbeitung ist auf Grund der Art der erhobenen Daten, des Verwendungszwecks,
|
||||
$# der automatischen Löschung und der Erhebungsgrundlagen nicht nötig (DSGVO Art.6 Abs.1 e+f).
|
||||
$# Ein Wiederrufsrecht zur Verarbeitung, Antragsrecht auf Löschung, Antragsrecht auf Berichtigung,
|
||||
$# Antragsrecht auf Übertragung ist wegen nicht nötiger Zustimmung zur Verarbeitung bzw. Art und Nutzung der erhobenen Daten nicht gegeben.
|
||||
$#
|
||||
$# <h4>Beschwerderecht
|
||||
$# <p>
|
||||
$# Nutzer können sich generell bzgl. jeder Verarbeitung oder Weitergabe von
|
||||
$# persönlichen Daten bei der Aufsichtbehörde beschweren.
|
||||
$# Im Fall der LMU ist dies der oben genannte bayerische Datenschutzbeauftragte.
|
||||
$# Ansonsten können auch alle anderen oben genannten Ansprechpartner
|
||||
$# bzgl. Beschwerden und Nachfragen kontaktiert werden.
|
||||
$#
|
||||
$# <h4>Verpflichtung zur Teilnahme an der Verarbeitung
|
||||
$# <p>
|
||||
$# Der Nutzer ist bei Nutzung dieses Dienstes verpflichtet die Daten bereitzustellen und
|
||||
$# verarbeiten zu lassen. Wir behalten uns das Recht vor,
|
||||
$# Nutzer, die die Daten nicht bereitstellen, von der Nutzung des Dienstes auszuschließen.
|
||||
$#
|
||||
23
templates/i18n/exam-users/computed-values-tip/en-eu.hamlet
Normal file
23
templates/i18n/exam-users/computed-values-tip/en-eu.hamlet
Normal file
@ -0,0 +1,23 @@
|
||||
$newline never
|
||||
<div .notification .notification-warning .fa-exclamation-triangle .notification--broad>
|
||||
<div .notification__content>
|
||||
<p>
|
||||
This table contains values that were computed automatically.
|
||||
<p>
|
||||
Values computed automatically (bonus and result) are shown to neither the #
|
||||
participant nor relevant exam offices until they are manually accepted.<br />
|
||||
To do this you may use the action “Accept computed result”.
|
||||
<p>
|
||||
You are also able to override the automatically computed values manually #
|
||||
(via CSV import).<br />
|
||||
|
||||
If values thus overriden do not match the automatically computed values #
|
||||
they are considered <i>inconsistent</i>.
|
||||
<p>
|
||||
Automatically computed values are marked as follows:
|
||||
|
||||
<table style="font-weight: normal">
|
||||
<tr>
|
||||
<td style="padding: 0 7px 0 0" .table__td .table__td--automatic>Automatically computed
|
||||
<td style="padding: 0 7px" .table__td>Normal value
|
||||
<td style="padding: 0 0 0 7px" .table__td .table__td--overriden>Inconsistent
|
||||
@ -5,5 +5,4 @@ $newline never
|
||||
<ul>
|
||||
<li> Stundenplan/Kalender mit allen Veranstaltungen und Klausuren
|
||||
<li> Vollständige Vorlesungshomepages
|
||||
<li> Vollständige Internationalisierung deutsch/englisch/...
|
||||
|
||||
7
templates/i18n/featureList/en-eu.hamlet
Normal file
7
templates/i18n/featureList/en-eu.hamlet
Normal file
@ -0,0 +1,7 @@
|
||||
$newline never
|
||||
<p>
|
||||
<h3>
|
||||
Planned functionally, currently only partially implemented
|
||||
<ul>
|
||||
<li> Timetable/calendar with all courses and exams
|
||||
<li> Complete course homepages
|
||||
@ -0,0 +1,7 @@
|
||||
$newline never
|
||||
<dd>
|
||||
Nutzer, die, um die Funktionstüchtigkeit des Systems zu erhalten, über #
|
||||
erweiterte Rechte innerhalb eines oder mehrerer Institute verfügen.<br />
|
||||
Haben vollen Zugriff auf alle Kurse und Funktionen innerhalb ihrer Institute #
|
||||
und auf einige systemweite Funktionalitäten wie z.B. das Ausstellen von #
|
||||
Uni2work-internen Kennungen.
|
||||
7
templates/i18n/glossary/administrator.en-eu.hamlet
Normal file
7
templates/i18n/glossary/administrator.en-eu.hamlet
Normal file
@ -0,0 +1,7 @@
|
||||
$newline never
|
||||
<dd>
|
||||
Users that have elevated permissions in one or more departments in order to #
|
||||
ensure the operational reliability of the system.<br />
|
||||
Have full access to all courses and functions inside of their department(s) #
|
||||
and to some system-wide functionalities, such as the creation of Uni2work
|
||||
accounts.
|
||||
7
templates/i18n/glossary/allocation.de-de-formal.hamlet
Normal file
7
templates/i18n/glossary/allocation.de-de-formal.hamlet
Normal file
@ -0,0 +1,7 @@
|
||||
$newline never
|
||||
<dd>
|
||||
Da verfügbare Kapazitäten in einigen Sorten von Veranstaltung (speziell #
|
||||
Praktika und Seminare) i.A. nicht hinreichend sind für alle Studierenden die #
|
||||
in einem bestimmten Semester an einer deartigen Veranstaltung teilnehmen #
|
||||
wollen oder müssen, werden die verfügbaren Plätze zentral auf die #
|
||||
Interessenten verteilt.
|
||||
6
templates/i18n/glossary/allocation.en-eu.hamlet
Normal file
6
templates/i18n/glossary/allocation.en-eu.hamlet
Normal file
@ -0,0 +1,6 @@
|
||||
$newline never
|
||||
<dd>
|
||||
Since the available capacity in some sorts of courses (especially practicals and seminars) #
|
||||
is limited and generally insufficient for the number of students that want/need to participate #
|
||||
in this sort of course in a specific semester, the capacity is distributed among all participants #
|
||||
of an allocation.
|
||||
8
templates/i18n/glossary/applicant.de-de-formal.hamlet
Normal file
8
templates/i18n/glossary/applicant.de-de-formal.hamlet
Normal file
@ -0,0 +1,8 @@
|
||||
$newline never
|
||||
<dt .sec>
|
||||
_{MsgAllocationApplication}
|
||||
<dt .sec>
|
||||
_{MsgCourseApply}
|
||||
<dd>
|
||||
Studierende können eine Bewerbung für einen Kurs hinterlegen.<br />
|
||||
Wird die Bewerbung akzeptiert (oder der Studierende dem Kurs zugeteilt) wird der Studierende zusätzlich Kursteilnehmer.
|
||||
9
templates/i18n/glossary/applicant.en-eu.hamlet
Normal file
9
templates/i18n/glossary/applicant.en-eu.hamlet
Normal file
@ -0,0 +1,9 @@
|
||||
$newline never
|
||||
<dt .sec>
|
||||
_{MsgAllocationApplication}
|
||||
<dt .sec>
|
||||
_{MsgCourseApply}
|
||||
<dd>
|
||||
Students can apply for specific courses.<br />
|
||||
The student will additionally become a course participant, should the application be accepted #
|
||||
(or should the student be allocated to the course).
|
||||
6
templates/i18n/glossary/clone.de-de-formal.hamlet
Normal file
6
templates/i18n/glossary/clone.de-de-formal.hamlet
Normal file
@ -0,0 +1,6 @@
|
||||
$newline never
|
||||
<dd>
|
||||
Einige Objekte in Uni2work (Kurse, Übungsblätter, ...) lassen sich klonen.<br />
|
||||
D.h. alle sinnvollen Einstellungen werden vom geklonten Objekt übernommen und #
|
||||
das Formular zum Erstellen eines neuen Objekts vom selben Typ mit den Daten #
|
||||
vorausgefüllt.
|
||||
5
templates/i18n/glossary/clone.en-eu.hamlet
Normal file
5
templates/i18n/glossary/clone.en-eu.hamlet
Normal file
@ -0,0 +1,5 @@
|
||||
$newline never
|
||||
<dd>
|
||||
Some objects in Uni2work (courses, exercise sheets, ...) can be cloned.<br />
|
||||
This means that all reasonable settings will be taken from the cloned object and #
|
||||
the form to create a new objects of this type will be pre-filled with these settings.
|
||||
6
templates/i18n/glossary/comm-course.de-de-formal.hamlet
Normal file
6
templates/i18n/glossary/comm-course.de-de-formal.hamlet
Normal file
@ -0,0 +1,6 @@
|
||||
$newline never
|
||||
<dt .sec>
|
||||
_{MsgCommTutorial}
|
||||
<dd>
|
||||
Kursverwalter und Tutoren haben die Möglichkeit über Uni2work E-Mails an ihre #
|
||||
Kurs- bzw. Tutoriumsteilnehmer zu verschicken.
|
||||
6
templates/i18n/glossary/comm-course.en-eu.hamlet
Normal file
6
templates/i18n/glossary/comm-course.en-eu.hamlet
Normal file
@ -0,0 +1,6 @@
|
||||
$newline never
|
||||
<dt .sec>
|
||||
_{MsgCommTutorial}
|
||||
<dd>
|
||||
Course administrators and tutors are able to send e-mails to their course/tutorial #
|
||||
participants via Uni2work.
|
||||
16
templates/i18n/glossary/corrector.de-de-formal.hamlet
Normal file
16
templates/i18n/glossary/corrector.de-de-formal.hamlet
Normal file
@ -0,0 +1,16 @@
|
||||
$newline never
|
||||
<dt .sec>
|
||||
_{MsgCorProportion}
|
||||
<dt .sec>
|
||||
_{MsgCorState}
|
||||
<dd>
|
||||
Nutzer, die von einem Kursverwalter beauftragt wurden bei der Korrektur von #
|
||||
einzelnen Übungsblättern oder Prüfungen mitzuwirken.<br />
|
||||
Beim Zuteilen für ein Übungsblatt kann ein Korrekturanteil festgelegt werden. #
|
||||
Bei der automatischen Verteilung von Korrekturen wird dann versucht die #
|
||||
eingestellten Anteile möglichst gut widerzuspiegeln.<br />
|
||||
Bei Übungsblättern gibt es die Möglichkeit Korrektoren zwar prinzipiell #
|
||||
zuzuteilen aber dann als abwesend oder entschuldigt zu markieren.<br />
|
||||
In beiden Fällen werden diesem Korrektor dann keine Abgaben zugeteilt.
|
||||
Ist der Korrektor nur abwesend werden die so nicht zugeteilten Abgaben jedoch #
|
||||
als Defizit vermerkt.
|
||||
15
templates/i18n/glossary/corrector.en-eu.hamlet
Normal file
15
templates/i18n/glossary/corrector.en-eu.hamlet
Normal file
@ -0,0 +1,15 @@
|
||||
$newline never
|
||||
<dt .sec>
|
||||
_{MsgCorProportion}
|
||||
<dt .sec>
|
||||
_{MsgCorState}
|
||||
<dd>
|
||||
Users that are authorised by a course administrator to collaborate in #
|
||||
the correction of single exercise sheets or exams.<br />
|
||||
A correction proportion can be set when assigning a corrector to an exercise sheet. #
|
||||
These proportions will then be followed as closely as possible during the next automated #
|
||||
distribution of corrections.<br />
|
||||
For exercise sheets, there is the option to assign correctors in principle, but then mark #
|
||||
them as missing or excused.<br />
|
||||
In both cases, these correctors will not be assigned to any submissions.
|
||||
In case of a missing corrector, those missed submissions will then be registered as deficit.
|
||||
@ -0,0 +1,14 @@
|
||||
$newline never
|
||||
<dd>
|
||||
<p>
|
||||
Wenn eine Veranstaltung ein Bewerbungsverfahren hat, werden, statt sich als #
|
||||
Student direkt für die Veranstaltung anzumelden, nur Bewerbungen an die #
|
||||
Veranstaltung gestellt.<br />
|
||||
Es ist dann Aufgabe der Kursverwalter die Bewerbungen zu bewerten und ggf. zu #
|
||||
akzeptieren.
|
||||
<p>
|
||||
Auch Veranstaltungen, die an einer Zentralanmeldung teilnehmen, können ein #
|
||||
Bewerbungsverfahren haben oder nicht.<br />
|
||||
In diesem Fall werden immer Bewerbungen (über die Zentralanmeldung) an den #
|
||||
Kurs gestellt, es werden jedoch die Einstellungen vom kurseigenen #
|
||||
Bewerbungsverfahren übernommen (z.B. ob die Abgabe von Dateien erlaubt ist).
|
||||
@ -0,0 +1,13 @@
|
||||
$newline never
|
||||
<dd>
|
||||
<p>
|
||||
If a course requires application, only applications of students will be registered #
|
||||
instead of students directly enrolling in the course.<br />
|
||||
It is then up to the course administrators to rate and (if applicable) accept them.
|
||||
<p>
|
||||
Courses that participate in a central allocation also have the option of requiring #
|
||||
applications.<br />
|
||||
In case of a central allocation, every course that participates in it will receive #
|
||||
applications, regardless of whether this option is set or not. However, course settings #
|
||||
regardings the application procedure (e.g. submissions of files, required filenames, ...) #
|
||||
will be applied.
|
||||
@ -0,0 +1,8 @@
|
||||
$newline never
|
||||
<dt .sec>
|
||||
_{MsgCourseApplicationTemplateRegistration}
|
||||
<dd>
|
||||
Wenn eine Veranstaltung ein Bewerbungsverfahren hat, dass die Abgabe von #
|
||||
Dateien erlaubt, gibt es für Kursverwalter die Möglichkeit einen Satz von #
|
||||
Dateien zu publizieren der potentiellen Bewerbern dabei helfen soll #
|
||||
Bewerbungen anzufertigen, die den Erwartungen der Kursverwalter entsprechen.
|
||||
@ -0,0 +1,8 @@
|
||||
$newline never
|
||||
<dt .sec>
|
||||
_{MsgCourseApplicationTemplateRegistration}
|
||||
<dd>
|
||||
If a course requires applications that allow for the submission of files, #
|
||||
the course administrators are able to publish a set of files that may help #
|
||||
future applicants in composing applications that meet the course administrator's #
|
||||
expectations.
|
||||
4
templates/i18n/glossary/course-exams.de-de-formal.hamlet
Normal file
4
templates/i18n/glossary/course-exams.de-de-formal.hamlet
Normal file
@ -0,0 +1,4 @@
|
||||
$newline never
|
||||
<dd>
|
||||
Es lassen sich quasi beliebige Prüfungsformen (Projektabnahmen, mündliche #
|
||||
Prüfungen, Klausuren, ...) in Uni2work modellieren.
|
||||
4
templates/i18n/glossary/course-exams.en-eu.hamlet
Normal file
4
templates/i18n/glossary/course-exams.en-eu.hamlet
Normal file
@ -0,0 +1,4 @@
|
||||
$newline never
|
||||
<dd>
|
||||
Quasi-arbitrary forms of exams (project reviews, oral exams, written exams, ...) #
|
||||
can be modelled in Uni2work.
|
||||
@ -0,0 +1,5 @@
|
||||
$newline never
|
||||
<dd>
|
||||
Nutzer die bei der Organisation einer Veranstaltung mitwirken und hierfür vom #
|
||||
Nutzer, der den Kurs im System angelegt hat, volle Kontrolle über alle Aspekte #
|
||||
der Veranstaltung erhalten haben.
|
||||
4
templates/i18n/glossary/course-lecturers.en-eu.hamlet
Normal file
4
templates/i18n/glossary/course-lecturers.en-eu.hamlet
Normal file
@ -0,0 +1,4 @@
|
||||
$newline never
|
||||
<dd>
|
||||
Users that help in organising a course and are therefore granted full control #
|
||||
(by the user that created the course) over every aspect of the course.
|
||||
@ -0,0 +1,5 @@
|
||||
$newline never
|
||||
<dd>
|
||||
Kursverwalter haben die Möglichkeit direkt in Uni2work zusätzliche #
|
||||
Kursunterlagen (z.B. Foliensätze oder Programmbeispiele) an ihre #
|
||||
Kursteilnehmer zu verteilen.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user