Merge branch 'master' into fix/split-message-files
This commit is contained in:
commit
690872d679
@ -194,3 +194,8 @@
|
|||||||
- warn: { lhs: n >= length xs, rhs: maxLength n xs, note: IncreasesLaziness }
|
- warn: { lhs: n >= length xs, rhs: maxLength n xs, note: IncreasesLaziness }
|
||||||
- warn: { lhs: length xs < n, rhs: maxLength (n - 1) xs, note: IncreasesLaziness }
|
- warn: { lhs: length xs < n, rhs: maxLength (n - 1) xs, note: IncreasesLaziness }
|
||||||
- warn: { lhs: n > length xs, rhs: maxLength (n - 1) xs, note: IncreasesLaziness }
|
- warn: { lhs: n > length xs, rhs: maxLength (n - 1) xs, note: IncreasesLaziness }
|
||||||
|
|
||||||
|
- error: {lhs: v Database.Esqueleto.==. Database.Esqueleto.nothing, rhs: Database.Esqueleto.isNothing v, name: Use Esqueleto's isNothing}
|
||||||
|
- error: {lhs: v Database.Esqueleto.==. Database.Esqueleto.val Nothing, rhs: Database.Esqueleto.isNothing v, name: Use Esqueleto's isNothing}
|
||||||
|
- error: {lhs: v Database.Esqueleto.!=. Database.Esqueleto.nothing, rhs: Database.Esqueleto.Utils.isJust v, name: Use Esqueleto's not isNothing}
|
||||||
|
- error: {lhs: v Database.Esqueleto.!=. Database.Esqueleto.val Nothing, rhs: Database.Esqueleto.Utils.isJust v, name: Use Esqueleto's not isNothing}
|
||||||
|
|||||||
15
CHANGELOG.md
15
CHANGELOG.md
@ -2,6 +2,21 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
## [25.9.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.9.0...v25.9.1) (2021-04-14)
|
||||||
|
|
||||||
|
## [25.9.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.8.1...v25.9.0) (2021-04-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* partial support for lsf import ([37cdc77](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/37cdc775b5b2d3e4cd1cc22858b2c05e75de8a3c)), closes [#686](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/686)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* build ([5c709f1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5c709f1bbb077d981fbd5d59e9c0f30cddbb468d))
|
||||||
|
* prevent deleting sheet-referenced exam parts ([9859c2e](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/9859c2e99c1e0c7531ee38864a24ff279a8e6a7c)), closes [#681](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/issues/681)
|
||||||
|
|
||||||
## [25.8.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.8.0...v25.8.1) (2021-04-09)
|
## [25.8.1](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.8.0...v25.8.1) (2021-04-09)
|
||||||
|
|
||||||
## [25.8.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.7.0...v25.8.0) (2021-04-08)
|
## [25.8.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v25.7.0...v25.8.0) (2021-04-08)
|
||||||
|
|||||||
@ -221,7 +221,7 @@ cookies:
|
|||||||
secure: "_env:COOKIES_SECURE:true"
|
secure: "_env:COOKIES_SECURE:true"
|
||||||
|
|
||||||
user-defaults:
|
user-defaults:
|
||||||
max-favourites: 12
|
max-favourites: 0
|
||||||
max-favourite-terms: 2
|
max-favourite-terms: 2
|
||||||
theme: Default
|
theme: Default
|
||||||
date-time-format: "%a %d %b %Y %R"
|
date-time-format: "%a %d %b %Y %R"
|
||||||
|
|||||||
@ -165,7 +165,7 @@ h4
|
|||||||
margin-top: var(--current-header-height)
|
margin-top: var(--current-header-height)
|
||||||
margin-left: 0
|
margin-left: 0
|
||||||
|
|
||||||
:target:not(table :target)::before
|
:target:not(table, .show-hide__toggle)::before
|
||||||
content: ""
|
content: ""
|
||||||
display: block
|
display: block
|
||||||
height: var(--current-header-height)
|
height: var(--current-header-height)
|
||||||
@ -275,6 +275,9 @@ button:not(.btn-link),
|
|||||||
display: grid
|
display: grid
|
||||||
grid: min-content / auto-flow max-content
|
grid: min-content / auto-flow max-content
|
||||||
|
|
||||||
|
.buttongroup--inline
|
||||||
|
display: inline-grid
|
||||||
|
|
||||||
input[type="submit"][disabled]:not(.btn-link),
|
input[type="submit"][disabled]:not(.btn-link),
|
||||||
input[type="button"][disabled]:not(.btn-link),
|
input[type="button"][disabled]:not(.btn-link),
|
||||||
button[disabled]:not(.btn-link),
|
button[disabled]:not(.btn-link),
|
||||||
@ -328,6 +331,10 @@ input[type="button"].btn-info:not(.btn-link):hover,
|
|||||||
&:not([disabled]):hover
|
&:not([disabled]):hover
|
||||||
color: var(--color-link-hover)
|
color: var(--color-link-hover)
|
||||||
|
|
||||||
|
// STACK ICON STYLE
|
||||||
|
.icon--stacked
|
||||||
|
font-size: 0.5rem
|
||||||
|
|
||||||
// GENERAL TABLE STYLES
|
// GENERAL TABLE STYLES
|
||||||
.table
|
.table
|
||||||
margin: 21px 0
|
margin: 21px 0
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
@use "../../common" as *
|
||||||
|
@use "../../app" as *
|
||||||
|
|
||||||
.main__aside
|
.main__aside
|
||||||
position: fixed
|
position: fixed
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3)
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3)
|
||||||
@ -90,6 +93,18 @@
|
|||||||
padding: 0 13px
|
padding: 0 13px
|
||||||
margin: 3px 0
|
margin: 3px 0
|
||||||
|
|
||||||
|
.asidenav__box-explanation
|
||||||
|
@extend .explanation
|
||||||
|
padding: 0 13px
|
||||||
|
margin: 3px 0
|
||||||
|
opacity: .66
|
||||||
|
font-size: .7rem
|
||||||
|
|
||||||
|
/* transition: opacity .2s ease, font-size .2s ease
|
||||||
|
/* &:hover
|
||||||
|
/* font-size: .9rem
|
||||||
|
/* opacity: 1
|
||||||
|
|
||||||
// LOGO
|
// LOGO
|
||||||
|
|
||||||
.asidenav__logo
|
.asidenav__logo
|
||||||
@ -217,9 +232,30 @@
|
|||||||
.asidenav__link-shorthand
|
.asidenav__link-shorthand
|
||||||
display: none
|
display: none
|
||||||
|
|
||||||
|
.asidenav__link-favourite-toggle
|
||||||
|
opacity: .33
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
opacity: 1
|
||||||
|
|
||||||
|
button
|
||||||
|
display: flex
|
||||||
|
text-decoration: none
|
||||||
|
|
||||||
.asidenav__link-label
|
.asidenav__link-label
|
||||||
|
display: flex
|
||||||
|
justify-content: space-between
|
||||||
|
align-items: center
|
||||||
line-height: 1
|
line-height: 1
|
||||||
|
|
||||||
|
& > .asidenav__link-label-text
|
||||||
|
word-break: break-word
|
||||||
|
flex: 1 1 auto
|
||||||
|
|
||||||
|
& > .asidenav__link-favourite-toggle
|
||||||
|
flex: 0 0 $fa-fw-width
|
||||||
|
margin: 0 5px
|
||||||
|
|
||||||
// hover sub-menus
|
// hover sub-menus
|
||||||
.asidenav__nested-list-wrapper
|
.asidenav__nested-list-wrapper
|
||||||
position: absolute
|
position: absolute
|
||||||
|
|||||||
@ -83,7 +83,6 @@ CosubmittorTip: Einladungen per E-Mail erhalten genau jene Adressen, für die ni
|
|||||||
CorrectionsTitle: Zugewiesene Korrekturen
|
CorrectionsTitle: Zugewiesene Korrekturen
|
||||||
CorrectorsHead sheetName@SheetName: Korrektoren für #{sheetName}
|
CorrectorsHead sheetName@SheetName: Korrektoren für #{sheetName}
|
||||||
|
|
||||||
<<<<<<< Updated upstream
|
|
||||||
MaterialName: Name
|
MaterialName: Name
|
||||||
MaterialType: Art
|
MaterialType: Art
|
||||||
MaterialTypePlaceholder: Folien, Code, Beispiel, ...
|
MaterialTypePlaceholder: Folien, Code, Beispiel, ...
|
||||||
@ -180,8 +179,6 @@ MaterialFree: Kursmaterialien ohne Anmeldung zugänglich
|
|||||||
UnauthorizedWrite: Sie haben hierfür keine Schreibberechtigung
|
UnauthorizedWrite: Sie haben hierfür keine Schreibberechtigung
|
||||||
UnauthorizedSystemMessageTime: Diese Systemnachricht ist noch nicht oder nicht mehr einsehbar.
|
UnauthorizedSystemMessageTime: Diese Systemnachricht ist noch nicht oder nicht mehr einsehbar.
|
||||||
UnauthorizedSystemMessageAuth: Diese Systemnachricht ist nur für angemeldete Benutzer einsehbar.
|
UnauthorizedSystemMessageAuth: Diese Systemnachricht ist nur für angemeldete Benutzer einsehbar.
|
||||||
=======
|
|
||||||
>>>>>>> Stashed changes
|
|
||||||
UnsupportedAuthPredicate authTagT@Text shownRoute@Text: „#{authTagT}“ wurde auf eine Route angewandt, die dies nicht unterstützt: „#{shownRoute}“
|
UnsupportedAuthPredicate authTagT@Text shownRoute@Text: „#{authTagT}“ wurde auf eine Route angewandt, die dies nicht unterstützt: „#{shownRoute}“
|
||||||
|
|
||||||
WorkflowRoleUserMismatch: Sie sind nicht einer der vom Workflow geforderten Benutzer
|
WorkflowRoleUserMismatch: Sie sind nicht einer der vom Workflow geforderten Benutzer
|
||||||
@ -491,12 +488,9 @@ CsvDeleteMissing: Fehlende Einträge entfernen
|
|||||||
TableProportion c@Text of'@Text prop@Rational: #{c}/#{of'} (#{rationalToFixed2 (100 * prop)}%)
|
TableProportion c@Text of'@Text prop@Rational: #{c}/#{of'} (#{rationalToFixed2 (100 * prop)}%)
|
||||||
TableProportionNoRatio c@Text of'@Text: #{c}/#{of'}
|
TableProportionNoRatio c@Text of'@Text: #{c}/#{of'}
|
||||||
|
|
||||||
<<<<<<< Updated upstream
|
|
||||||
ParticipantsCsvName tid@TermId ssh@SchoolId: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-kursteilnehmer
|
ParticipantsCsvName tid@TermId ssh@SchoolId: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-kursteilnehmer
|
||||||
ParticipantsCsvSheetName tid@TermId ssh@SchoolId: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)} Kursteilnehmer
|
ParticipantsCsvSheetName tid@TermId ssh@SchoolId: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)} Kursteilnehmer
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> Stashed changes
|
|
||||||
CsvColumnUserField: Studienfach, mit dem der Teilnehmer seine Kursanmeldung assoziiert hat
|
CsvColumnUserField: Studienfach, mit dem der Teilnehmer seine Kursanmeldung assoziiert hat
|
||||||
CsvColumnUserDegree: Abschluss, den der Teilnehmer im assoziierten Studienfach anstrebt
|
CsvColumnUserDegree: Abschluss, den der Teilnehmer im assoziierten Studienfach anstrebt
|
||||||
CsvColumnUserSemester: Fachsemester des Teilnehmers im assoziierten Studienfach
|
CsvColumnUserSemester: Fachsemester des Teilnehmers im assoziierten Studienfach
|
||||||
@ -581,8 +575,13 @@ FavouriteParticipant: Ihre Kurse
|
|||||||
FavouriteManual: Favoriten
|
FavouriteManual: Favoriten
|
||||||
FavouriteCurrent: Aktueller Kurs
|
FavouriteCurrent: Aktueller Kurs
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
=======
|
=======
|
||||||
>>>>>>> Stashed changes
|
>>>>>>> Stashed changes
|
||||||
|
=======
|
||||||
|
FavouritesEmptyTip: Hier werden Ihre Kurse, sowie zuletzt besuchte Kurse angezeigt.
|
||||||
|
FavouritesToggleTip: Der Anzeigemodus für den aktuellen Kurs kann über einen Klick auf das Stern-Symbol zwischen automatisch, permanent und nie gewechselt werden.
|
||||||
|
>>>>>>> master
|
||||||
FavouritesUnavailableTip: Das Schnellzugriffsmenü für diesen Kurs ist aktuell nicht verfügbar.
|
FavouritesUnavailableTip: Das Schnellzugriffsmenü für diesen Kurs ist aktuell nicht verfügbar.
|
||||||
|
|
||||||
|
|
||||||
@ -669,3 +668,15 @@ UrlFieldCouldNotParseAbsolute: Konnte nicht als absolute URL interpretiert werde
|
|||||||
|
|
||||||
WGFTextInput: Textfeld
|
WGFTextInput: Textfeld
|
||||||
WGFFileUpload: Dateifeld
|
WGFFileUpload: Dateifeld
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
WorkflowGraphFormUploadIsDirectory: Upload ist Verzeichnis
|
||||||
|
WorkflowGraphFormInvalidNumberOfFiles: Es muss genau eine Datei hochgeladen werden
|
||||||
|
|
||||||
|
CourseSortingOnlyLoggedIn: Das Benutzerinterface zur Sortierung dieser Tabelle ist nur für eingeloggte Benutzer aktiv
|
||||||
|
|
||||||
|
CorrectionInvisibleExamUnfinished: Die Frist „_{MsgExamFinished}“ für die relevante Prüfung ist noch nicht verstrichen
|
||||||
|
CorrectionInvisibleRatingNotDone: Die Bewertung ist nicht als „Abgeschlossen“ markiert
|
||||||
|
CorrectionInvisibleWarning: Die Bewertung dieser Abgabe ist aktuell für mindestens eine an der Abgabe beteiligte Person nicht sichtbar!
|
||||||
|
CorrectionInvisibleReasons: Mögliche Gründe hierfür:
|
||||||
|
>>>>>>> master
|
||||||
|
|||||||
@ -86,7 +86,6 @@ CosubmittorTip: Invitations are sent via email to exactly those addresses for wh
|
|||||||
CorrectionsTitle: Assigned corrections
|
CorrectionsTitle: Assigned corrections
|
||||||
CorrectorsHead sheetName: Correctors for #{sheetName}
|
CorrectorsHead sheetName: Correctors for #{sheetName}
|
||||||
|
|
||||||
<<<<<<< Updated upstream
|
|
||||||
MaterialName: Name
|
MaterialName: Name
|
||||||
MaterialType: Type
|
MaterialType: Type
|
||||||
MaterialTypePlaceholder: Slides, Code, Example, ...
|
MaterialTypePlaceholder: Slides, Code, Example, ...
|
||||||
@ -182,8 +181,6 @@ MaterialFree: Course material is publicly available.
|
|||||||
UnauthorizedWrite: You do not have the write permission necessary to perform this action
|
UnauthorizedWrite: You do not have the write permission necessary to perform this action
|
||||||
UnauthorizedSystemMessageTime: This system-message is not currently available.
|
UnauthorizedSystemMessageTime: This system-message is not currently available.
|
||||||
UnauthorizedSystemMessageAuth: This system-message is only available to logged in users.
|
UnauthorizedSystemMessageAuth: This system-message is only available to logged in users.
|
||||||
=======
|
|
||||||
>>>>>>> Stashed changes
|
|
||||||
UnsupportedAuthPredicate authTagT shownRoute: “#{authTagT}” was applied to a route which does not support it: “#{shownRoute}”
|
UnsupportedAuthPredicate authTagT shownRoute: “#{authTagT}” was applied to a route which does not support it: “#{shownRoute}”
|
||||||
|
|
||||||
WorkflowRoleUserMismatch: You aren't any of the users authorized by the workflow
|
WorkflowRoleUserMismatch: You aren't any of the users authorized by the workflow
|
||||||
@ -493,12 +490,9 @@ CsvDeleteMissing: Delete missing entries
|
|||||||
TableProportion c of' prop: #{c}/#{of'} (#{rationalToFixed2 (100 * prop)}%)
|
TableProportion c of' prop: #{c}/#{of'} (#{rationalToFixed2 (100 * prop)}%)
|
||||||
TableProportionNoRatio c of': #{c}/#{of'}
|
TableProportionNoRatio c of': #{c}/#{of'}
|
||||||
|
|
||||||
<<<<<<< Updated upstream
|
|
||||||
ParticipantsCsvName tid ssh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-participants
|
ParticipantsCsvName tid ssh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)}-participants
|
||||||
ParticipantsCsvSheetName tid ssh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)} Participants
|
ParticipantsCsvSheetName tid ssh: #{foldCase (termToText (unTermKey tid))}-#{foldedCase (unSchoolKey ssh)} Participants
|
||||||
|
|
||||||
=======
|
|
||||||
>>>>>>> Stashed changes
|
|
||||||
CsvColumnUserField: Field of study the participant specified when enrolling for the course
|
CsvColumnUserField: Field of study the participant specified when enrolling for the course
|
||||||
CsvColumnUserDegree: Degree the participant pursues in their associated field of study
|
CsvColumnUserDegree: Degree the participant pursues in their associated field of study
|
||||||
CsvColumnUserSemester: Semester the participant is in wrt. to their associated field of study
|
CsvColumnUserSemester: Semester the participant is in wrt. to their associated field of study
|
||||||
@ -583,8 +577,13 @@ FavouriteParticipant: Your courses
|
|||||||
FavouriteManual: Favourites
|
FavouriteManual: Favourites
|
||||||
FavouriteCurrent: Current course
|
FavouriteCurrent: Current course
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
=======
|
=======
|
||||||
>>>>>>> Stashed changes
|
>>>>>>> Stashed changes
|
||||||
|
=======
|
||||||
|
FavouritesEmptyTip: Your courses and recently visited courses are shown here.
|
||||||
|
FavouritesToggleTip: The display mode for the current course can be changed between automatic, permanent and never with a click on the star symbol.
|
||||||
|
>>>>>>> master
|
||||||
FavouritesUnavailableTip: Quick Actions for this course are currently not available.
|
FavouritesUnavailableTip: Quick Actions for this course are currently not available.
|
||||||
|
|
||||||
UserSimplifiedFeaturesOfStudyCsv: Simplified features of study
|
UserSimplifiedFeaturesOfStudyCsv: Simplified features of study
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uni2work",
|
"name": "uni2work",
|
||||||
"version": "25.8.1",
|
"version": "25.9.1",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "uni2work",
|
"name": "uni2work",
|
||||||
"version": "25.8.1",
|
"version": "25.9.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
name: uniworx
|
name: uniworx
|
||||||
version: 25.8.1
|
version: 25.9.1
|
||||||
dependencies:
|
dependencies:
|
||||||
- base
|
- base
|
||||||
- yesod
|
- yesod
|
||||||
|
|||||||
2
routes
2
routes
@ -174,7 +174,7 @@
|
|||||||
!/course/new CourseNewR GET POST !lecturer
|
!/course/new CourseNewR GET POST !lecturer
|
||||||
/course/#TermId/#SchoolId/#CourseShorthand CourseR !lecturer:
|
/course/#TermId/#SchoolId/#CourseShorthand CourseR !lecturer:
|
||||||
/ CShowR GET !tutor !corrector !exam-corrector !course-registered !course-time !evaluation !exam-office !allocation-admin
|
/ CShowR GET !tutor !corrector !exam-corrector !course-registered !course-time !evaluation !exam-office !allocation-admin
|
||||||
/favourite CFavouriteR POST
|
/favourite CFavouriteR GET POST !free
|
||||||
/register CRegisterR GET POST !timeANDcapacityANDallocation-timeAND¬course-registeredANDcourse-time !timeANDallocation-timeAND¬exam-resultANDcourse-registered !lecturerANDallocation-time
|
/register CRegisterR GET POST !timeANDcapacityANDallocation-timeAND¬course-registeredANDcourse-time !timeANDallocation-timeAND¬exam-resultANDcourse-registered !lecturerANDallocation-time
|
||||||
/register-template CRegisterTemplateR GET !course-time
|
/register-template CRegisterTemplateR GET !course-time
|
||||||
/edit CEditR GET POST
|
/edit CEditR GET POST
|
||||||
|
|||||||
@ -32,6 +32,7 @@ module Database.Esqueleto.Utils
|
|||||||
, selectMaybe
|
, selectMaybe
|
||||||
, day, diffDays, diffTimes
|
, day, diffDays, diffTimes
|
||||||
, exprLift
|
, exprLift
|
||||||
|
, explicitUnsafeCoerceSqlExprValue
|
||||||
, module Database.Esqueleto.Utils.TH
|
, module Database.Esqueleto.Utils.TH
|
||||||
) where
|
) where
|
||||||
|
|
||||||
@ -55,6 +56,8 @@ import Data.Coerce (Coercible)
|
|||||||
|
|
||||||
import Data.Time.Clock (NominalDiffTime)
|
import Data.Time.Clock (NominalDiffTime)
|
||||||
|
|
||||||
|
import qualified Data.Text.Lazy.Builder as Text.Builder
|
||||||
|
|
||||||
{-# ANN any ("HLint: ignore Use any" :: String) #-}
|
{-# ANN any ("HLint: ignore Use any" :: String) #-}
|
||||||
{-# ANN all ("HLint: ignore Use all" :: String) #-}
|
{-# ANN all ("HLint: ignore Use all" :: String) #-}
|
||||||
|
|
||||||
@ -130,6 +133,17 @@ substring (E.ERaw p1 f1) (E.ERaw p2 f2) (E.ERaw p3 f3)
|
|||||||
)
|
)
|
||||||
substring a b c = substring (construct a) (construct b) (construct c)
|
substring a b c = substring (construct a) (construct b) (construct c)
|
||||||
|
|
||||||
|
explicitUnsafeCoerceSqlExprValue :: forall b a.
|
||||||
|
Text
|
||||||
|
-> E.SqlExpr (E.Value a)
|
||||||
|
-> E.SqlExpr (E.Value b)
|
||||||
|
explicitUnsafeCoerceSqlExprValue typ (E.ERaw p1 f1) = E.ERaw E.Parens $ \info ->
|
||||||
|
let (valTLB, valVals) = f1 info
|
||||||
|
in ( E.parensM p1 valTLB <> " :: " <> Text.Builder.fromText typ
|
||||||
|
, valVals
|
||||||
|
)
|
||||||
|
explicitUnsafeCoerceSqlExprValue typ val = explicitUnsafeCoerceSqlExprValue typ $ construct val
|
||||||
|
|
||||||
construct :: E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value a)
|
construct :: E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value a)
|
||||||
construct (E.ERaw p f) = E.ERaw E.Parens $ \info ->
|
construct (E.ERaw p f) = E.ERaw E.Parens $ \info ->
|
||||||
let (b1, vals) = f info
|
let (b1, vals) = f info
|
||||||
|
|||||||
@ -10,7 +10,6 @@ module Foundation.Instances
|
|||||||
import Import.NoFoundation
|
import Import.NoFoundation
|
||||||
|
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
import qualified Data.List as List
|
|
||||||
import Data.List (inits)
|
import Data.List (inits)
|
||||||
|
|
||||||
import qualified Yesod.Core.Unsafe as Unsafe
|
import qualified Yesod.Core.Unsafe as Unsafe
|
||||||
@ -28,6 +27,7 @@ import qualified Foundation.Yesod.StaticContent as UniWorX
|
|||||||
import qualified Foundation.Yesod.Persist as UniWorX
|
import qualified Foundation.Yesod.Persist as UniWorX
|
||||||
import qualified Foundation.Yesod.Auth as UniWorX
|
import qualified Foundation.Yesod.Auth as UniWorX
|
||||||
|
|
||||||
|
import Foundation.Instances.ButtonClass
|
||||||
import Foundation.SiteLayout
|
import Foundation.SiteLayout
|
||||||
import Foundation.Type
|
import Foundation.Type
|
||||||
import Foundation.I18n
|
import Foundation.I18n
|
||||||
@ -51,29 +51,6 @@ import qualified Data.CaseInsensitive as CI
|
|||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
|
|
||||||
data instance ButtonClass UniWorX
|
|
||||||
= BCIsButton
|
|
||||||
| BCDefault
|
|
||||||
| BCPrimary
|
|
||||||
| BCSuccess
|
|
||||||
| BCInfo
|
|
||||||
| BCWarning
|
|
||||||
| BCDanger
|
|
||||||
| BCLink
|
|
||||||
| BCMassInputAdd | BCMassInputDelete
|
|
||||||
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
|
|
||||||
deriving anyclass (Universe, Finite)
|
|
||||||
|
|
||||||
instance PathPiece (ButtonClass UniWorX) where
|
|
||||||
toPathPiece BCIsButton = "btn"
|
|
||||||
toPathPiece bClass = ("btn-" <>) . camelToPathPiece' 1 $ tshow bClass
|
|
||||||
fromPathPiece = flip List.lookup $ map (toPathPiece &&& id) universeF
|
|
||||||
|
|
||||||
instance Button UniWorX ButtonSubmit where
|
|
||||||
btnClasses BtnSubmit = [BCIsButton, BCPrimary]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- Please see the documentation for the Yesod typeclass. There are a number
|
-- Please see the documentation for the Yesod typeclass. There are a number
|
||||||
-- of settings which can be configured by overriding methods here.
|
-- of settings which can be configured by overriding methods here.
|
||||||
instance Yesod UniWorX where
|
instance Yesod UniWorX where
|
||||||
|
|||||||
33
src/Foundation/Instances/ButtonClass.hs
Normal file
33
src/Foundation/Instances/ButtonClass.hs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
module Foundation.Instances.ButtonClass (ButtonClass(..)) where
|
||||||
|
|
||||||
|
import Import.NoFoundation
|
||||||
|
|
||||||
|
import Utils.Form
|
||||||
|
import Foundation.Type
|
||||||
|
import qualified Data.List as List
|
||||||
|
|
||||||
|
-- instance RenderMessage UniWorX ButtonSubmit
|
||||||
|
import Foundation.I18n ()
|
||||||
|
|
||||||
|
|
||||||
|
data instance ButtonClass UniWorX
|
||||||
|
= BCIsButton
|
||||||
|
| BCDefault
|
||||||
|
| BCPrimary
|
||||||
|
| BCSuccess
|
||||||
|
| BCInfo
|
||||||
|
| BCWarning
|
||||||
|
| BCDanger
|
||||||
|
| BCLink
|
||||||
|
| BCMassInputAdd | BCMassInputDelete
|
||||||
|
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
|
||||||
|
deriving anyclass (Universe, Finite)
|
||||||
|
|
||||||
|
instance PathPiece (ButtonClass UniWorX) where
|
||||||
|
toPathPiece BCIsButton = "btn"
|
||||||
|
toPathPiece bClass = ("btn-" <>) . camelToPathPiece' 1 $ tshow bClass
|
||||||
|
fromPathPiece = flip List.lookup $ map (toPathPiece &&& id) universeF
|
||||||
|
|
||||||
|
instance Button UniWorX ButtonSubmit where
|
||||||
|
btnClasses BtnSubmit = [BCIsButton, BCPrimary]
|
||||||
@ -5,6 +5,7 @@ module Foundation.SiteLayout
|
|||||||
( siteLayout', siteLayout
|
( siteLayout', siteLayout
|
||||||
, siteLayoutMsg', siteLayoutMsg
|
, siteLayoutMsg', siteLayoutMsg
|
||||||
, getSystemMessageState
|
, getSystemMessageState
|
||||||
|
, storedFavouriteReason
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Import.NoFoundation hiding (embedFile, runDB)
|
import Import.NoFoundation hiding (embedFile, runDB)
|
||||||
@ -15,6 +16,7 @@ import Foundation.Routes
|
|||||||
import Foundation.Navigation
|
import Foundation.Navigation
|
||||||
import Foundation.I18n
|
import Foundation.I18n
|
||||||
import Foundation.Yesod.Persist
|
import Foundation.Yesod.Persist
|
||||||
|
import Foundation.Instances.ButtonClass
|
||||||
|
|
||||||
import Utils.SystemMessage
|
import Utils.SystemMessage
|
||||||
import Utils.Form
|
import Utils.Form
|
||||||
@ -37,6 +39,61 @@ import Text.Cassius (cassiusFile)
|
|||||||
import Text.Hamlet (hamletFile)
|
import Text.Hamlet (hamletFile)
|
||||||
import Data.FileEmbed (embedFile)
|
import Data.FileEmbed (embedFile)
|
||||||
|
|
||||||
|
data CourseFavouriteToggleButton
|
||||||
|
= BtnCourseFavouriteToggleManual
|
||||||
|
| BtnCourseFavouriteToggleAutomatic
|
||||||
|
| BtnCourseFavouriteToggleOff
|
||||||
|
deriving (Eq, Ord, Read, Show, Enum, Bounded, Generic, Typeable)
|
||||||
|
deriving anyclass (Universe, Finite)
|
||||||
|
|
||||||
|
nullaryPathPiece ''CourseFavouriteToggleButton $ camelToPathPiece' 4
|
||||||
|
|
||||||
|
instance Button UniWorX CourseFavouriteToggleButton where
|
||||||
|
btnLabel BtnCourseFavouriteToggleManual
|
||||||
|
= toWidget $ iconFixed IconCourseFavouriteManual
|
||||||
|
btnLabel BtnCourseFavouriteToggleAutomatic
|
||||||
|
= toWidget $ iconFixed IconCourseFavouriteAutomatic
|
||||||
|
btnLabel BtnCourseFavouriteToggleOff
|
||||||
|
= toWidget $ iconStacked IconCourseFavouriteManual IconCourseFavouriteOff
|
||||||
|
|
||||||
|
btnClasses _ = [BCIsButton, BCLink]
|
||||||
|
|
||||||
|
-- inspired by examAutoOccurrenceIgnoreRoomsForm
|
||||||
|
courseFavouriteToggleForm :: Maybe FavouriteReason -> Form ()
|
||||||
|
courseFavouriteToggleForm currentReason html
|
||||||
|
= over _1 void <$> identifyForm FIDCourseFavouriteToggle (buttonForm' [btn]) html
|
||||||
|
where
|
||||||
|
btn :: CourseFavouriteToggleButton
|
||||||
|
btn = case currentReason of
|
||||||
|
Nothing -> BtnCourseFavouriteToggleOff
|
||||||
|
(Just FavouriteVisited) -> BtnCourseFavouriteToggleAutomatic
|
||||||
|
(Just FavouriteParticipant) -> BtnCourseFavouriteToggleAutomatic
|
||||||
|
(Just FavouriteManual) -> BtnCourseFavouriteToggleManual
|
||||||
|
(Just FavouriteCurrent) -> BtnCourseFavouriteToggleAutomatic
|
||||||
|
|
||||||
|
-- (storedReason, isBlacklist)
|
||||||
|
-- Will never return FavouriteCurrent
|
||||||
|
-- Nothing if no entry for current user (e.g. not logged in)
|
||||||
|
storedFavouriteReason :: (MonadIO m, BearerAuthSite UniWorX) => TermId -> SchoolId -> CourseShorthand -> Maybe (AuthId UniWorX, AuthEntity UniWorX)
|
||||||
|
-> ReaderT SqlBackend m (Maybe (Maybe FavouriteReason, Bool))
|
||||||
|
storedFavouriteReason tid ssh csh muid = fmap unValueFirst . E.select . E.from $ \(course `E.LeftOuterJoin` courseFavourite) -> do
|
||||||
|
E.on $ E.just (course E.^. CourseId) E.==. courseFavourite E.?. CourseFavouriteCourse
|
||||||
|
E.&&. courseFavourite E.?. CourseFavouriteUser E.==. E.val (view _1 <$> muid)
|
||||||
|
E.where_ $ course E.^. CourseTerm E.==. E.val tid
|
||||||
|
E.&&. course E.^. CourseSchool E.==. E.val ssh
|
||||||
|
E.&&. course E.^. CourseShorthand E.==. E.val csh
|
||||||
|
E.limit 1 -- we know that there is at most one match, but we tell the DB this info too
|
||||||
|
let isBlacklist = E.exists . E.from $ \courseNoFavourite ->
|
||||||
|
E.where_ $ E.just (courseNoFavourite E.^. CourseNoFavouriteUser) E.==. E.val (view _1 <$> muid)
|
||||||
|
E.&&. courseNoFavourite E.^. CourseNoFavouriteCourse E.==. course E.^. CourseId
|
||||||
|
reason :: (E.SqlExpr (E.Value (Maybe FavouriteReason)), E.SqlExpr (E.Value Bool))
|
||||||
|
reason = (courseFavourite E.?. CourseFavouriteReason, isBlacklist)
|
||||||
|
pure reason
|
||||||
|
where
|
||||||
|
unValueFirst :: [(E.Value (Maybe a), E.Value Bool)] -> Maybe (Maybe a, Bool)
|
||||||
|
-- `over each E.unValue` doesn't work here, since E.unValue is monomorphised
|
||||||
|
unValueFirst = fmap (bimap E.unValue E.unValue) . listToMaybe
|
||||||
|
|
||||||
|
|
||||||
data MemcachedKeyFavourites
|
data MemcachedKeyFavourites
|
||||||
= MemcachedKeyFavouriteQuickActions (TermId, SchoolId, CourseShorthand) AuthContext (NonEmpty Lang)
|
= MemcachedKeyFavouriteQuickActions (TermId, SchoolId, CourseShorthand) AuthContext (NonEmpty Lang)
|
||||||
@ -54,16 +111,15 @@ data MemcachedLimitKeyFavourites
|
|||||||
deriving anyclass (Hashable, Binary)
|
deriving anyclass (Hashable, Binary)
|
||||||
|
|
||||||
|
|
||||||
siteLayoutMsg :: (RenderMessage site msg, site ~ UniWorX, BearerAuthSite UniWorX, YesodPersistBackend UniWorX ~ SqlBackend, Button UniWorX ButtonSubmit) => msg -> WidgetFor UniWorX () -> HandlerFor UniWorX Html
|
siteLayoutMsg :: (RenderMessage site msg, site ~ UniWorX, BearerAuthSite UniWorX, YesodPersistBackend UniWorX ~ SqlBackend) => msg -> WidgetFor UniWorX () -> HandlerFor UniWorX Html
|
||||||
siteLayoutMsg = siteLayout . i18n
|
siteLayoutMsg = siteLayout . i18n
|
||||||
|
|
||||||
{-# DEPRECATED siteLayoutMsg' "Use siteLayoutMsg" #-}
|
{-# DEPRECATED siteLayoutMsg' "Use siteLayoutMsg" #-}
|
||||||
siteLayoutMsg' :: (RenderMessage site msg, site ~ UniWorX, BearerAuthSite UniWorX, YesodPersistBackend UniWorX ~ SqlBackend, Button UniWorX ButtonSubmit) => msg -> WidgetFor UniWorX () -> HandlerFor UniWorX Html
|
siteLayoutMsg' :: (RenderMessage site msg, site ~ UniWorX, BearerAuthSite UniWorX, YesodPersistBackend UniWorX ~ SqlBackend) => msg -> WidgetFor UniWorX () -> HandlerFor UniWorX Html
|
||||||
siteLayoutMsg' = siteLayoutMsg
|
siteLayoutMsg' = siteLayoutMsg
|
||||||
|
|
||||||
siteLayout :: ( BearerAuthSite UniWorX
|
siteLayout :: ( BearerAuthSite UniWorX
|
||||||
, YesodPersistBackend UniWorX ~ SqlBackend
|
, YesodPersistBackend UniWorX ~ SqlBackend
|
||||||
, Button UniWorX ButtonSubmit
|
|
||||||
)
|
)
|
||||||
=> WidgetFor UniWorX () -- ^ `pageHeading`
|
=> WidgetFor UniWorX () -- ^ `pageHeading`
|
||||||
-> WidgetFor UniWorX () -> HandlerFor UniWorX Html
|
-> WidgetFor UniWorX () -> HandlerFor UniWorX Html
|
||||||
@ -71,7 +127,6 @@ siteLayout = siteLayout' . Just
|
|||||||
|
|
||||||
siteLayout' :: ( BearerAuthSite UniWorX
|
siteLayout' :: ( BearerAuthSite UniWorX
|
||||||
, YesodPersistBackend UniWorX ~ SqlBackend
|
, YesodPersistBackend UniWorX ~ SqlBackend
|
||||||
, Button UniWorX ButtonSubmit
|
|
||||||
)
|
)
|
||||||
=> Maybe (WidgetFor UniWorX ()) -- ^ `pageHeading`
|
=> Maybe (WidgetFor UniWorX ()) -- ^ `pageHeading`
|
||||||
-> WidgetFor UniWorX () -> HandlerFor UniWorX Html
|
-> WidgetFor UniWorX () -> HandlerFor UniWorX Html
|
||||||
@ -94,11 +149,11 @@ siteLayout' overrideHeading widget = do
|
|||||||
|
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
|
|
||||||
|
muid <- maybeAuthPair
|
||||||
-- Lookup Favourites, Breadcrumbs, Headline, & Theme if possible
|
-- Lookup Favourites, Breadcrumbs, Headline, & Theme if possible
|
||||||
(favourites', (title, parents), nav', contentHeadline, mmsgs, maxFavouriteTerms, currentTheme) <- do
|
(favourites', (title, parents), nav', contentHeadline, mmsgs, maxFavouriteTerms, currentTheme, storedReasonAndToggleRoute) <- do
|
||||||
muid <- maybeAuthPair
|
|
||||||
|
|
||||||
(favCourses, breadcrumbs'', nav', contentHeadline, mmsgs) <- runDB $ do
|
(favCourses, breadcrumbs'', nav', contentHeadline, mmsgs, storedReasonAndToggleRoute) <- runDB $ do
|
||||||
favCourses'' <- withReaderT (projectBackend @SqlReadBackend) . E.select . E.from $ \(course `E.LeftOuterJoin` courseFavourite) -> do
|
favCourses'' <- withReaderT (projectBackend @SqlReadBackend) . E.select . E.from $ \(course `E.LeftOuterJoin` courseFavourite) -> do
|
||||||
E.on $ E.just (course E.^. CourseId) E.==. courseFavourite E.?. CourseFavouriteCourse
|
E.on $ E.just (course E.^. CourseId) E.==. courseFavourite E.?. CourseFavouriteCourse
|
||||||
E.&&. courseFavourite E.?. CourseFavouriteUser E.==. E.val (view _1 <$> muid)
|
E.&&. courseFavourite E.?. CourseFavouriteUser E.==. E.val (view _1 <$> muid)
|
||||||
@ -189,8 +244,12 @@ siteLayout' overrideHeading widget = do
|
|||||||
forM_ authTagPivots $
|
forM_ authTagPivots $
|
||||||
\authTag -> addMessageWidget Info $ msgModal [whamlet|_{MsgUnauthorizedDisabledTag authTag}|] (Left $ SomeRoute (AuthPredsR, catMaybes [(toPathPiece GetReferer, ) . toPathPiece <$> mcurrentRoute]))
|
\authTag -> addMessageWidget Info $ msgModal [whamlet|_{MsgUnauthorizedDisabledTag authTag}|] (Left $ SomeRoute (AuthPredsR, catMaybes [(toPathPiece GetReferer, ) . toPathPiece <$> mcurrentRoute]))
|
||||||
getMessages
|
getMessages
|
||||||
|
|
||||||
|
storedReasonAndToggleRoute <- case mcurrentRoute of
|
||||||
|
(Just (CourseR tid ssh csh _)) -> (, Just . SomeRoute $ CourseR tid ssh csh CFavouriteR) <$> storedFavouriteReason tid ssh csh muid
|
||||||
|
_otherwise -> pure (Nothing, Nothing)
|
||||||
|
|
||||||
return (favCourses, breadcrumbs'', nav', contentHeadline, mmsgs)
|
return (favCourses, breadcrumbs'', nav', contentHeadline, mmsgs, storedReasonAndToggleRoute)
|
||||||
|
|
||||||
return ( favCourses
|
return ( favCourses
|
||||||
, breadcrumbs''
|
, breadcrumbs''
|
||||||
@ -199,10 +258,37 @@ siteLayout' overrideHeading widget = do
|
|||||||
, mmsgs
|
, mmsgs
|
||||||
, maybe userDefaultMaxFavouriteTerms userMaxFavouriteTerms $ view _2 <$> muid
|
, maybe userDefaultMaxFavouriteTerms userMaxFavouriteTerms $ view _2 <$> muid
|
||||||
, maybe userDefaultTheme userTheme $ view _2 <$> muid
|
, maybe userDefaultTheme userTheme $ view _2 <$> muid
|
||||||
|
, storedReasonAndToggleRoute
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let (currentReason', maybeRoute) = storedReasonAndToggleRoute
|
||||||
|
currentReason = case currentReason' of
|
||||||
|
-- (reason, blacklist)
|
||||||
|
(Just (_reason, True)) -> Nothing
|
||||||
|
(Just (Just reason, False)) -> Just reason
|
||||||
|
(Just (Nothing, False)) -> Just FavouriteCurrent
|
||||||
|
Nothing -> Just FavouriteCurrent
|
||||||
|
showFavToggle :: FavouriteReason -> Bool
|
||||||
|
showFavToggle FavouriteCurrent = isJust muid
|
||||||
|
showFavToggle _favouriteReason = False
|
||||||
|
favouriteToggleRes <- runFormPost $ courseFavouriteToggleForm currentReason
|
||||||
|
let favouriteToggleWgt = favouriteToggleRes & \((_, favouriteToggleView), favouriteToggleEncoding) ->
|
||||||
|
wrapForm favouriteToggleView def
|
||||||
|
{ formAction = maybeRoute
|
||||||
|
, formEncoding = favouriteToggleEncoding
|
||||||
|
, formSubmit = FormNoSubmit
|
||||||
|
, formAttrs = [("class", "buttongroup buttongroup--inline")]
|
||||||
|
}
|
||||||
|
|
||||||
let favouriteTerms :: [TermIdentifier]
|
let favouriteTerms :: [TermIdentifier]
|
||||||
favouriteTerms = take maxFavouriteTerms . Set.toDescList $ foldMap (\((_, tid, _, _), _, _, _, _) -> Set.singleton $ unTermKey tid) favourites'
|
favouriteTerms = Set.toDescList . prune $ toTermKeySet favourites'
|
||||||
|
where
|
||||||
|
prune ts = currentTerms `Set.union` setTakeEnd (maxFavouriteTerms - Set.size currentTerms) (ts `Set.difference` currentTerms)
|
||||||
|
setTakeEnd n ts
|
||||||
|
| n <= 0 = Set.empty
|
||||||
|
| otherwise = Set.drop (Set.size ts - n) ts
|
||||||
|
currentTerms = toTermKeySet $ filter (views (_2 . _Value) . maybe True $ is _FavouriteCurrent) favourites'
|
||||||
|
toTermKeySet = setOf $ folded . _1 . _2 . to unTermKey
|
||||||
|
|
||||||
favourites <- fmap catMaybes . forM favourites' $ \(c@(_, tid, ssh, csh), E.Value mFavourite, courseVisible, mayView, mayEdit)
|
favourites <- fmap catMaybes . forM favourites' $ \(c@(_, tid, ssh, csh), E.Value mFavourite, courseVisible, mayView, mayEdit)
|
||||||
-> let courseRoute = CourseR tid ssh csh CShowR
|
-> let courseRoute = CourseR tid ssh csh CShowR
|
||||||
@ -251,6 +337,8 @@ siteLayout' overrideHeading widget = do
|
|||||||
favouriteTermReason tid favReason' = favourites
|
favouriteTermReason tid favReason' = favourites
|
||||||
& filter (\((_, tid', _, _), _, _, favReason, _, _, _) -> unTermKey tid' == tid && favReason == favReason')
|
& filter (\((_, tid', _, _), _, _, favReason, _, _, _) -> unTermKey tid' == tid && favReason == favReason')
|
||||||
& sortOn (\((cName, _, _, _), _, _, _, _, _, _) -> cName)
|
& sortOn (\((cName, _, _, _), _, _, _, _, _, _) -> cName)
|
||||||
|
anyFavToggle = flip any ((,) <$> universeF <*> favouriteTerms) $ \(reason, term) ->
|
||||||
|
showFavToggle reason && not (null $ favouriteTermReason term reason)
|
||||||
|
|
||||||
-- We break up the default layout into two components:
|
-- We break up the default layout into two components:
|
||||||
-- default-layout is the contents of the body tag, and
|
-- default-layout is the contents of the body tag, and
|
||||||
|
|||||||
@ -185,7 +185,8 @@ updateFavourites cData = void . withReaderT projectBackend . runMaybeT $ do
|
|||||||
User{userMaxFavourites} <- MaybeT $ get uid
|
User{userMaxFavourites} <- MaybeT $ get uid
|
||||||
|
|
||||||
-- update Favourites
|
-- update Favourites
|
||||||
for_ mcid $ \cid ->
|
-- no need to store them with userMaxFavourites==0, since they will be removed in the pruning step anyway!
|
||||||
|
when (userMaxFavourites > 0) $ for_ mcid $ \cid ->
|
||||||
void . lift $ upsertBy
|
void . lift $ upsertBy
|
||||||
(UniqueCourseFavourite uid cid)
|
(UniqueCourseFavourite uid cid)
|
||||||
(CourseFavourite uid cid FavouriteVisited now)
|
(CourseFavourite uid cid FavouriteVisited now)
|
||||||
|
|||||||
@ -4,6 +4,8 @@ module Handler.Course
|
|||||||
|
|
||||||
import Import
|
import Import
|
||||||
|
|
||||||
|
import qualified Database.Esqueleto as E
|
||||||
|
import qualified Database.Persist as P
|
||||||
|
|
||||||
import Handler.Course.Communication as Handler.Course
|
import Handler.Course.Communication as Handler.Course
|
||||||
import Handler.Course.Delete as Handler.Course
|
import Handler.Course.Delete as Handler.Course
|
||||||
@ -32,5 +34,35 @@ getCNotesR, postCNotesR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
|||||||
getCNotesR = postCNotesR
|
getCNotesR = postCNotesR
|
||||||
postCNotesR _ _ _ = defaultLayout [whamlet|You have corrector access to this course.|]
|
postCNotesR _ _ _ = defaultLayout [whamlet|You have corrector access to this course.|]
|
||||||
|
|
||||||
|
-- simple redirect for now to avoid running into HTTP method not supported.
|
||||||
|
getCFavouriteR :: TermId -> SchoolId -> CourseShorthand -> Handler ()
|
||||||
|
getCFavouriteR tid ssh csh = redirect $ CourseR tid ssh csh CShowR
|
||||||
postCFavouriteR :: TermId -> SchoolId -> CourseShorthand -> Handler ()
|
postCFavouriteR :: TermId -> SchoolId -> CourseShorthand -> Handler ()
|
||||||
postCFavouriteR _ _ _ = error "not implemented"
|
postCFavouriteR tid ssh csh = void $ do
|
||||||
|
authPair@(uid, _) <- requireAuthPair
|
||||||
|
runDB $ void $ do
|
||||||
|
cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
-- should never return FavouriteCurrent
|
||||||
|
newReason <- storedFavouriteReason tid ssh csh (Just authPair) <&> (\case
|
||||||
|
-- Maybe (Maybe reason, blacklist)
|
||||||
|
Nothing -> Just FavouriteManual
|
||||||
|
Just (_reason, True) -> Just FavouriteVisited
|
||||||
|
Just (Just FavouriteManual, False) -> Nothing
|
||||||
|
Just (_reason, False) -> Just FavouriteManual)
|
||||||
|
-- change stored reason in DB
|
||||||
|
case newReason of
|
||||||
|
(Just reason) -> do
|
||||||
|
void $ E.upsertBy
|
||||||
|
(UniqueCourseFavourite uid cid)
|
||||||
|
(CourseFavourite uid cid reason now)
|
||||||
|
[P.Update CourseFavouriteReason reason P.Assign]
|
||||||
|
E.deleteBy $ UniqueCourseNoFavourite uid cid
|
||||||
|
Nothing -> do
|
||||||
|
E.deleteBy $ UniqueCourseFavourite uid cid
|
||||||
|
void $ E.upsertBy
|
||||||
|
(UniqueCourseNoFavourite uid cid)
|
||||||
|
(CourseNoFavourite uid cid)
|
||||||
|
[] -- entry shouldn't exists, but keep it unchanged anyway
|
||||||
|
-- show course page again
|
||||||
|
redirect $ CourseR tid ssh csh CShowR
|
||||||
|
|||||||
@ -14,6 +14,18 @@ import Handler.Utils.Invitations
|
|||||||
|
|
||||||
import Jobs.Queue
|
import Jobs.Queue
|
||||||
|
|
||||||
|
import qualified Database.Esqueleto as E
|
||||||
|
import qualified Database.Esqueleto.Utils as E
|
||||||
|
|
||||||
|
|
||||||
|
data ExamEditException
|
||||||
|
= ExamEditExamNameTaken ExamName
|
||||||
|
| ExamEditWouldBreakSheetTypeReference
|
||||||
|
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||||
|
deriving anyclass (Exception)
|
||||||
|
|
||||||
|
embedRenderMessage ''UniWorX ''ExamEditException id
|
||||||
|
|
||||||
|
|
||||||
getEEditR, postEEditR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html
|
getEEditR, postEEditR :: TermId -> SchoolId -> CourseShorthand -> ExamName -> Handler Html
|
||||||
getEEditR = postEEditR
|
getEEditR = postEEditR
|
||||||
@ -27,31 +39,34 @@ postEEditR tid ssh csh examn = do
|
|||||||
((editExamResult, editExamWidget), editExamEnctype) <- runFormPost . validateForm (validateExam cid $ Just exam) . examForm course $ Just template
|
((editExamResult, editExamWidget), editExamEnctype) <- runFormPost . validateForm (validateExam cid $ Just exam) . examForm course $ Just template
|
||||||
|
|
||||||
editExamAct <- formResultMaybe editExamResult $ \ExamForm{..} -> do
|
editExamAct <- formResultMaybe editExamResult $ \ExamForm{..} -> do
|
||||||
insertRes <- myReplaceUnique eId Exam
|
res <- trySql @ExamEditException $ do
|
||||||
{ examCourse = cid
|
insertRes <- myReplaceUnique eId Exam
|
||||||
, examName = efName
|
{ examCourse = cid
|
||||||
, examGradingRule = efGradingRule
|
, examName = efName
|
||||||
, examBonusRule = efBonusRule
|
, examGradingRule = efGradingRule
|
||||||
, examOccurrenceRule = efOccurrenceRule
|
, examBonusRule = efBonusRule
|
||||||
, examExamOccurrenceMapping = examExamOccurrenceMapping oldExam
|
, examOccurrenceRule = efOccurrenceRule
|
||||||
, examVisibleFrom = efVisibleFrom
|
, examExamOccurrenceMapping = examExamOccurrenceMapping oldExam
|
||||||
, examRegisterFrom = efRegisterFrom
|
, examVisibleFrom = efVisibleFrom
|
||||||
, examRegisterTo = efRegisterTo
|
, examRegisterFrom = efRegisterFrom
|
||||||
, examDeregisterUntil = efDeregisterUntil
|
, examRegisterTo = efRegisterTo
|
||||||
, examPublishOccurrenceAssignments = efPublishOccurrenceAssignments
|
, examDeregisterUntil = efDeregisterUntil
|
||||||
, examStart = efStart
|
, examPublishOccurrenceAssignments = efPublishOccurrenceAssignments
|
||||||
, examEnd = efEnd
|
, examStart = efStart
|
||||||
, examFinished = efFinished
|
, examEnd = efEnd
|
||||||
, examClosed = examClosed oldExam
|
, examFinished = efFinished
|
||||||
, examPublicStatistics = efPublicStatistics
|
, examClosed = examClosed oldExam
|
||||||
, examGradingMode = efGradingMode
|
, examPublicStatistics = efPublicStatistics
|
||||||
, examDescription = efDescription
|
, examGradingMode = efGradingMode
|
||||||
, examExamMode = efExamMode
|
, examDescription = efDescription
|
||||||
, examStaff = efStaff
|
, examExamMode = efExamMode
|
||||||
, examPartsFrom = efPartsFrom
|
, examStaff = efStaff
|
||||||
}
|
, examPartsFrom = efPartsFrom
|
||||||
|
}
|
||||||
|
|
||||||
|
when (is _Just insertRes) $
|
||||||
|
throwM $ ExamEditExamNameTaken efName
|
||||||
|
|
||||||
when (is _Nothing insertRes) $ do
|
|
||||||
occIds <- fmap catMaybes . forM (Set.toList efOccurrences) $ traverse decrypt . eofId
|
occIds <- fmap catMaybes . forM (Set.toList efOccurrences) $ traverse decrypt . eofId
|
||||||
deleteWhere [ ExamOccurrenceExam ==. eId, ExamOccurrenceId /<-. occIds ]
|
deleteWhere [ ExamOccurrenceExam ==. eId, ExamOccurrenceId /<-. occIds ]
|
||||||
forM_ (Set.toList efOccurrences) $ \case
|
forM_ (Set.toList efOccurrences) $ \case
|
||||||
@ -83,6 +98,21 @@ postEEditR tid ssh csh examn = do
|
|||||||
}
|
}
|
||||||
|
|
||||||
pIds <- fmap catMaybes . forM (Set.toList efExamParts) $ traverse decrypt . epfId
|
pIds <- fmap catMaybes . forM (Set.toList efExamParts) $ traverse decrypt . epfId
|
||||||
|
|
||||||
|
brokenRefs <- E.selectExists . E.from $ \examPart -> do
|
||||||
|
E.where_ $ examPart E.^. ExamPartExam E.==. E.val eId
|
||||||
|
E.&&. examPart E.^. ExamPartId `E.notIn` E.valList pIds
|
||||||
|
E.where_ . E.exists . E.from $ \sheet -> do
|
||||||
|
let
|
||||||
|
sheetTypeExamPart :: E.SqlExpr (E.Value (Maybe Value))
|
||||||
|
sheetTypeExamPart = sheet E.^. SheetType E.->. "exam-part"
|
||||||
|
examPartId' :: E.SqlExpr (E.Value Value)
|
||||||
|
examPartId' = E.explicitUnsafeCoerceSqlExprValue @Value "jsonb" . E.explicitUnsafeCoerceSqlExprValue @Text "text" $ examPart E.^. ExamPartId
|
||||||
|
E.where_ $ E.maybe E.false (E.==. examPartId') sheetTypeExamPart
|
||||||
|
|
||||||
|
when brokenRefs $
|
||||||
|
throwM ExamEditWouldBreakSheetTypeReference
|
||||||
|
|
||||||
deleteWhere [ ExamPartExam ==. eId, ExamPartId /<-. pIds ]
|
deleteWhere [ ExamPartExam ==. eId, ExamPartId /<-. pIds ]
|
||||||
forM_ (Set.toList efExamParts) $ \case
|
forM_ (Set.toList efExamParts) $ \case
|
||||||
ExamPartForm{ epfId = Nothing, .. } -> insert_
|
ExamPartForm{ epfId = Nothing, .. } -> insert_
|
||||||
@ -118,9 +148,11 @@ postEEditR tid ssh csh examn = do
|
|||||||
deleteWhere [ InvitationFor ==. invRef @ExamCorrector eId, InvitationEmail /<-. invites ]
|
deleteWhere [ InvitationFor ==. invRef @ExamCorrector eId, InvitationEmail /<-. invites ]
|
||||||
sinkInvitationsF examCorrectorInvitationConfig $ map (, eId, (InvDBDataExamCorrector, InvTokenDataExamCorrector)) invites
|
sinkInvitationsF examCorrectorInvitationConfig $ map (, eId, (InvDBDataExamCorrector, InvTokenDataExamCorrector)) invites
|
||||||
|
|
||||||
return . Just $ case insertRes of
|
return insertRes
|
||||||
Just _ -> addMessageI Error $ MsgExamNameTaken efName
|
|
||||||
Nothing -> do
|
return . Just $ case res of
|
||||||
|
Left exc -> addMessageI Error exc
|
||||||
|
Right _ -> do
|
||||||
addMessageI Success $ MsgExamEdited efName
|
addMessageI Success $ MsgExamEdited efName
|
||||||
redirect $ CExamR tid ssh csh efName EShowR
|
redirect $ CExamR tid ssh csh efName EShowR
|
||||||
|
|
||||||
|
|||||||
@ -98,7 +98,7 @@ postCExamNewR tid ssh csh = do
|
|||||||
runConduit $ selectSource [ CourseParticipantCourse ==. cid, CourseParticipantState ==. CourseParticipantInactive True ] [] .| C.mapM_ recordNoShow
|
runConduit $ selectSource [ CourseParticipantCourse ==. cid, CourseParticipantState ==. CourseParticipantInactive True ] [] .| C.mapM_ recordNoShow
|
||||||
|
|
||||||
return . Just $ case insertRes of
|
return . Just $ case insertRes of
|
||||||
Nothing -> addMessageI Error $ MsgExamNameTaken efName
|
Nothing -> addMessageI Error $ MsgExamEditExamNameTaken efName
|
||||||
Just _ -> do
|
Just _ -> do
|
||||||
addMessageI Success $ MsgExamCreated efName
|
addMessageI Success $ MsgExamCreated efName
|
||||||
redirect $ CourseR tid ssh csh CExamListR
|
redirect $ CourseR tid ssh csh CExamListR
|
||||||
|
|||||||
@ -14,7 +14,10 @@ import qualified Database.Esqueleto.Utils as E
|
|||||||
|
|
||||||
import Development.GitRev
|
import Development.GitRev
|
||||||
|
|
||||||
import Auth.LDAP (ADError(..), ADInvalidCredentials(..))
|
import Auth.LDAP (ADError(..), ADInvalidCredentials(..), CampusMessage(..))
|
||||||
|
|
||||||
|
import Yesod.Auth.Message(AuthMessage(..))
|
||||||
|
|
||||||
|
|
||||||
-- | Versionsgeschichte
|
-- | Versionsgeschichte
|
||||||
getVersionR :: Handler TypedContent
|
getVersionR :: Handler TypedContent
|
||||||
|
|||||||
@ -54,8 +54,6 @@ $(deriveSimpleWith ''ToMessage 'toMessage (over Text.packed $ Text.intercalate "
|
|||||||
derivePersistField "Theme"
|
derivePersistField "Theme"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
data FavouriteReason
|
data FavouriteReason
|
||||||
= FavouriteVisited
|
= FavouriteVisited
|
||||||
| FavouriteParticipant
|
| FavouriteParticipant
|
||||||
@ -68,6 +66,9 @@ deriveJSON defaultOptions
|
|||||||
} ''FavouriteReason
|
} ''FavouriteReason
|
||||||
derivePersistFieldJSON ''FavouriteReason
|
derivePersistFieldJSON ''FavouriteReason
|
||||||
|
|
||||||
|
makePrisms ''FavouriteReason
|
||||||
|
|
||||||
|
|
||||||
data Sex
|
data Sex
|
||||||
= SexNotKnown
|
= SexNotKnown
|
||||||
| SexMale
|
| SexMale
|
||||||
|
|||||||
@ -198,6 +198,7 @@ data FormIdentifier
|
|||||||
| FIDmaterial
|
| FIDmaterial
|
||||||
| FIDCourseNews
|
| FIDCourseNews
|
||||||
| FIDCourseEvent
|
| FIDCourseEvent
|
||||||
|
| FIDCourseFavouriteToggle
|
||||||
| FIDsubmission
|
| FIDsubmission
|
||||||
| FIDsettings
|
| FIDsettings
|
||||||
| FIDcorrectors
|
| FIDcorrectors
|
||||||
|
|||||||
@ -37,6 +37,7 @@ data Icon
|
|||||||
| IconVisible
|
| IconVisible
|
||||||
| IconInvisible
|
| IconInvisible
|
||||||
| IconCourse
|
| IconCourse
|
||||||
|
| IconCourseFavouriteManual | IconCourseFavouriteAutomatic | IconCourseFavouriteOff
|
||||||
| IconEnrolTrue
|
| IconEnrolTrue
|
||||||
| IconEnrolFalse
|
| IconEnrolFalse
|
||||||
| IconPlanned
|
| IconPlanned
|
||||||
@ -110,6 +111,9 @@ iconText = \case
|
|||||||
IconVisible -> "eye"
|
IconVisible -> "eye"
|
||||||
IconInvisible -> "eye-slash"
|
IconInvisible -> "eye-slash"
|
||||||
IconCourse -> "graduation-cap"
|
IconCourse -> "graduation-cap"
|
||||||
|
IconCourseFavouriteManual -> "star"
|
||||||
|
IconCourseFavouriteAutomatic -> "star-half-alt"
|
||||||
|
IconCourseFavouriteOff -> "slash" -- TODO use FA regular style star for stacked icon
|
||||||
IconEnrolTrue -> "user-plus"
|
IconEnrolTrue -> "user-plus"
|
||||||
IconEnrolFalse -> "user-slash"
|
IconEnrolFalse -> "user-slash"
|
||||||
IconPlanned -> "cog"
|
IconPlanned -> "cog"
|
||||||
@ -189,6 +193,23 @@ icon ic = [shamlet|
|
|||||||
$newline never
|
$newline never
|
||||||
<i .fas .fa-#{iconText ic}>
|
<i .fas .fa-#{iconText ic}>
|
||||||
|]
|
|]
|
||||||
|
|
||||||
|
-- Create an icon from font-awesome with fixed width
|
||||||
|
iconFixed :: Icon -> Markup
|
||||||
|
iconFixed ic = [shamlet|
|
||||||
|
$newline never
|
||||||
|
<i .fas .fa-fw .fa-#{iconText ic}>
|
||||||
|
|]
|
||||||
|
|
||||||
|
-- Stack two icons from font-awesome without additional space
|
||||||
|
iconStacked :: Icon -> Icon -> Markup
|
||||||
|
iconStacked ic0 ic1
|
||||||
|
= [shamlet|
|
||||||
|
$newline never
|
||||||
|
<span .fa-stack .icon--stacked>
|
||||||
|
<i .fas .fa-stack-2x .fa-#{iconText ic0}>
|
||||||
|
<i .fas .fa-stack-2x .fa-#{iconText ic1}>
|
||||||
|
|]
|
||||||
|
|
||||||
-- Create an icon (defaults to "?") with a specified tooltip
|
-- Create an icon (defaults to "?") with a specified tooltip
|
||||||
iconTooltip :: forall site. WidgetFor site () -> Maybe Icon -> Bool -> WidgetFor site ()
|
iconTooltip :: forall site. WidgetFor site () -> Maybe Icon -> Bool -> WidgetFor site ()
|
||||||
|
|||||||
@ -5,6 +5,7 @@ module Utils.Sql
|
|||||||
, catchSql, handleSql
|
, catchSql, handleSql
|
||||||
, isUniqueConstraintViolation
|
, isUniqueConstraintViolation
|
||||||
, catchIfSql, handleIfSql
|
, catchIfSql, handleIfSql
|
||||||
|
, trySql
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ClassyPrelude.Yesod hiding (handle)
|
import ClassyPrelude.Yesod hiding (handle)
|
||||||
@ -125,5 +126,8 @@ catchIfSql p = flip $ handleIfSql p
|
|||||||
handleIfSql :: forall e m a. (MonadCatch m, MonadIO m, Exception e) => (e -> Bool) -> (e -> SqlPersistT m a) -> SqlPersistT m a -> SqlPersistT m a
|
handleIfSql :: forall e m a. (MonadCatch m, MonadIO m, Exception e) => (e -> Bool) -> (e -> SqlPersistT m a) -> SqlPersistT m a -> SqlPersistT m a
|
||||||
handleIfSql p recover = handleSql (\err -> bool throwM recover (p err) err)
|
handleIfSql p recover = handleSql (\err -> bool throwM recover (p err) err)
|
||||||
|
|
||||||
|
trySql :: forall e m a. (MonadCatch m, MonadIO m, Exception e) => SqlPersistT m a -> SqlPersistT m (Either e a)
|
||||||
|
trySql = handleSql (return . Left) . fmap Right
|
||||||
|
|
||||||
isUniqueConstraintViolation :: SqlError -> Bool
|
isUniqueConstraintViolation :: SqlError -> Bool
|
||||||
isUniqueConstraintViolation SqlError{..} = "duplicate key value violates unique constraint" `ByteString.isPrefixOf` sqlErrorMsg
|
isUniqueConstraintViolation SqlError{..} = "duplicate key value violates unique constraint" `ByteString.isPrefixOf` sqlErrorMsg
|
||||||
|
|||||||
@ -4,32 +4,66 @@ $newline never
|
|||||||
Können sie sich mit <i>exakt identischen</i> (idealerweise #
|
Können sie sich mit <i>exakt identischen</i> (idealerweise #
|
||||||
copy&paste) Daten #
|
copy&paste) Daten #
|
||||||
im <a href="https://www.portal.uni-muenchen.de">Campus-Portal</a> #
|
im <a href="https://www.portal.uni-muenchen.de">Campus-Portal</a> #
|
||||||
anmelden?<br />
|
anmelden?
|
||||||
|
|
||||||
Falls nicht, ist davon auszugehen, dass Sie Ihre Anmeldedaten falsch #
|
<br>
|
||||||
eingeben oder <a href=^{faqLink FAQNoCampusAccount}>keine #
|
|
||||||
LMU-Benutzerkennung (ehem. Campus-Kennung) besitzen</a>.
|
Falls nicht („_{InvalidLogin}“), ist davon auszugehen, dass Sie #
|
||||||
|
Ihre Anmeldedaten falsch eingeben oder #
|
||||||
|
<a href=^{faqLink FAQNoCampusAccount}>keine LMU-Benutzerkennung #
|
||||||
|
(ehem. Campus-Kennung) besitzen</a>.
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Beachten Sie dabei auch, dass Uni2work Leerzeichen sowohl im #
|
Beachten Sie dabei auch, dass Uni2work Leerzeichen sowohl im #
|
||||||
Passwort als auch bei der Kennung berücksichtigt.<br />
|
Passwort als auch bei der Kennung berücksichtigt.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
Beim Passwort ist zudem Groß- und Kleinschreibung relevant.
|
Beim Passwort ist zudem Groß- und Kleinschreibung relevant.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Prüfen Sie bitte (auch wenn Sie einen Passwortmanager verwenden) mit #
|
||||||
|
dem Auge-Symbol rechts neben dem Passwort-Feld (nur verfügbar mit #
|
||||||
|
aktiviertem JavaScript), dass Sie Ihr Passwort korrekt eingeben.
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Uni2work bietet zwei Login-Formulare.
|
Uni2work bietet zwei Login-Formulare.
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Für die Anmeldung mit der LMU-Benutzerkennung (ehem. Campus-Kennung) #
|
Für die Anmeldung mit der LMU-Benutzerkennung (ehem. Campus-Kennung) #
|
||||||
müssen Sie das Formular „Campus-Login“ verwenden.
|
müssen Sie das Formular „_{MsgLDAPLoginTitle}“ verwenden.
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
Geben Sie unter „Campus-Kennung“ ihre vollständige #
|
|
||||||
LMU-Benutzerkennung an.
|
Geben Sie unter „_{MsgCampusIdent}“ ihre vollständige #
|
||||||
|
LMU-Benutzerkennung an. #
|
||||||
|
|
||||||
Diese ist identisch mit ihrer <code>@campus.lmu.de</code> E-Mail #
|
Diese ist identisch mit ihrer <code>@campus.lmu.de</code> E-Mail #
|
||||||
Adresse.
|
Adresse.
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Falls Sie sich #
|
Falls Sie seit Ihrem letzten Login in Uni2work ihr Passwort geändert #
|
||||||
im <a href="https://www.portal.uni-muenchen.de">Campus-Portal</a> #
|
haben, kann es sein, dass die Änderung des Passworts (noch) nicht #
|
||||||
anmelden können, aber nicht in Uni2work, wenden Sie sich bitte über #
|
korrekt propagiert wurde.
|
||||||
das <a href=@{HelpR}>Hilfe-Formular</a>, oben rechts auf jeder #
|
|
||||||
Seite, an die Uni2work-Administration.
|
<br>
|
||||||
|
|
||||||
|
In diesem Fall können Sie versuchen Ihr Passwort erneut zu ändern.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Sobald Sie die obigen Hinweise befolgt haben, wenden Sie sich bitte #
|
||||||
|
(erneut) über das <a href=@{HelpR}>Hilfe-Formular</a>, oben rechts #
|
||||||
|
auf jeder Seite, an die Uni2work-Administration.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Erwähnen Sie dabei bitte, dass Sie die in diesem FAQ-Punkt #
|
||||||
|
aufgeführten Hinweise beachtet haben und schildern Sie, welche #
|
||||||
|
Schritte Sie bereits ergriffen haben.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Teilen Sie Ihr Passwort niemals mit Dritten (auch nicht der #
|
||||||
|
Uni2work-Administration oder dem IT-Servicedesk der LMU)!
|
||||||
|
|||||||
@ -4,31 +4,68 @@ $newline never
|
|||||||
Can you log in to #
|
Can you log in to #
|
||||||
the <a href="https://www.portal.uni-muenchen.de">Campus-Portal</a> #
|
the <a href="https://www.portal.uni-muenchen.de">Campus-Portal</a> #
|
||||||
using the <i>exact same</i> (ideally copied & pasted) login data?
|
using the <i>exact same</i> (ideally copied & pasted) login data?
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
If you cannot (“Invalid Login”), this means that you are entering #
|
If you cannot (“_{InvalidLogin}”), this means that you are #
|
||||||
your login data wrong or that you #
|
entering your login data wrong or that you #
|
||||||
<a href=^{faqLink FAQNoCampusAccount}>do not have a LMU user ID #
|
<a href=^{faqLink FAQNoCampusAccount}>do not have a LMU user ID #
|
||||||
(formerly Campus-ID)</a>.
|
(formerly Campus-ID)</a>.
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Please consider that for Uni2work both your user ID and password are #
|
Please consider that for Uni2work both your user ID and password are #
|
||||||
sensitive to whitespace characters.
|
sensitive to whitespace characters.
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
Your password is also case-sensitive.
|
Your password is also case-sensitive.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Please also use the eye-symbol next to the password field to check #
|
||||||
|
that you have entered your password correctly. #
|
||||||
|
|
||||||
|
The eye-symbol is only visible if JavaScript is activated in your #
|
||||||
|
browser. #
|
||||||
|
|
||||||
|
Please check your password in this way, even if you are using a #
|
||||||
|
password manager instead of typing it manually.
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Uni2work offers two login forms.
|
Uni2work offers two login forms.
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
To log in using your LMU user ID (formerly Campus-ID) you need to #
|
To log in using your LMU user ID (formerly Campus-ID) you need to #
|
||||||
use the form titled “Campus login”.
|
use the form titled “_{MsgLDAPLoginTitle}”.
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
Under “Campus account” please enter either your entire LMU user ID, #
|
|
||||||
which is identical to your <code>@campus.lmu.de</code> email address.
|
Under “_{MsgCampusIdent}” please enter your entire LMU user ID, #
|
||||||
|
which is identical to your <code>@campus.lmu.de</code> email #
|
||||||
|
address.
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
If you can log in to #
|
If you have changed your password since last you logged into #
|
||||||
the <a href="https://www.portal.uni-muenchen.de">Campus-Portal</a> #
|
Uni2work, it may be the case that your password change was not #
|
||||||
but can't log in to Uni2work, please contact a #
|
propagated properly.
|
||||||
Uni2work-Administrator using the <a href=@{HelpR}>Support form</a> #
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
If so, please try changing your password again.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Once you have followed the suggestions above, please contact a #
|
||||||
|
Uni2work-administrator using the <a href=@{HelpR}>Support form</a> #
|
||||||
(at the top right of every page).
|
(at the top right of every page).
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Please include that you have read this faq-entry and which steps you #
|
||||||
|
have already taken.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Never disclose your password to third parties! #
|
||||||
|
|
||||||
|
Not even to an Uni2work-administrator or the IT-Servicedesk!
|
||||||
|
|||||||
@ -9,35 +9,46 @@ $newline never
|
|||||||
_{MsgLogo}
|
_{MsgLogo}
|
||||||
|
|
||||||
<div .asidenav>
|
<div .asidenav>
|
||||||
$forall tid <- favouriteTerms
|
$if null favouriteTerms && is _Just muid
|
||||||
<div .asidenav__box>
|
<div .asidenav__box-explanation>
|
||||||
<h3 .asidenav__box-title uw-show-hide data-show-hide-id="#{termToText tid}" data-show-hide-align=right>
|
_{MsgFavouritesEmptyTip}
|
||||||
<div .asidenav-term-identifier--long>
|
$else
|
||||||
_{ShortTermIdentifier tid}
|
$forall tid <- favouriteTerms
|
||||||
<div .asidenav-term-identifier--short>
|
<div .asidenav__box>
|
||||||
#{toPathPiece tid}
|
<h3 .asidenav__box-title uw-show-hide data-show-hide-id="#{termToText tid}" data-show-hide-align=right>
|
||||||
$forall favReason <- sortOn Down universeF
|
<div .asidenav-term-identifier--long>
|
||||||
$if not (null $ favouriteTermReason tid favReason)
|
_{ShortTermIdentifier tid}
|
||||||
<h3 .asidenav__box-subtitle>
|
<div .asidenav-term-identifier--short>
|
||||||
_{favReason}
|
#{toPathPiece tid}
|
||||||
<ul .asidenav__list.list--iconless>
|
$forall favReason <- sortOn Down universeF
|
||||||
$forall ((cName, _, _, csh), courseRoute, mPageActions, _, courseVisible, _, mayEdit) <- favouriteTermReason tid favReason
|
$if not (null $ favouriteTermReason tid favReason)
|
||||||
<li .asidenav__list-item :highlight courseRoute:.asidenav__list-item--active>
|
<h3 .asidenav__box-subtitle>
|
||||||
<a .asidenav__link-wrapper href=@{courseRoute}>
|
_{favReason}
|
||||||
<div .asidenav__link-shorthand>#{csh}
|
<ul .asidenav__list.list--iconless>
|
||||||
<div .asidenav__link-label>
|
$forall ((cName, _, _, csh), courseRoute, mPageActions, _, courseVisible, _, mayEdit) <- favouriteTermReason tid favReason
|
||||||
#{cName}
|
<li .asidenav__list-item :highlight courseRoute:.asidenav__list-item--active>
|
||||||
$if mayEdit && not courseVisible
|
<a .asidenav__link-wrapper href=@{courseRoute}>
|
||||||
\ #{iconInvisible}
|
<div .asidenav__link-shorthand>#{csh}
|
||||||
<div .asidenav__nested-list-wrapper>
|
<div .asidenav__link-label>
|
||||||
$maybe pageActions <- mPageActions
|
<div .asidenav__link-label-text>
|
||||||
<ul .asidenav__nested-list.list--iconless>
|
#{cName}
|
||||||
$forall (label, route) <- pageActions
|
$if mayEdit && not courseVisible
|
||||||
<li .asidenav__nested-list-item>
|
\ #{iconInvisible}
|
||||||
<a .asidenav__link-wrapper href=#{route}>#{label}
|
$if showFavToggle favReason
|
||||||
$nothing
|
<div .asidenav__link-favourite-toggle>
|
||||||
<p .asidenav__nested-list--unavailable>
|
^{favouriteToggleWgt}
|
||||||
_{MsgFavouritesUnavailableTip}
|
<div .asidenav__nested-list-wrapper>
|
||||||
|
$maybe pageActions <- mPageActions
|
||||||
|
<ul .asidenav__nested-list.list--iconless>
|
||||||
|
$forall (label, route) <- pageActions
|
||||||
|
<li .asidenav__nested-list-item>
|
||||||
|
<a .asidenav__link-wrapper href=#{route}>#{label}
|
||||||
|
$nothing
|
||||||
|
<p .asidenav__nested-list--unavailable>
|
||||||
|
_{MsgFavouritesUnavailableTip}
|
||||||
|
$if anyFavToggle
|
||||||
|
<div .asidenav__box-explanation>
|
||||||
|
_{MsgFavouritesToggleTip}
|
||||||
|
|
||||||
<div .asidenav__sigillum>
|
<div .asidenav__sigillum>
|
||||||
<img src=@{StaticR img_lmu_sigillum_svg}>
|
<img src=@{StaticR img_lmu_sigillum_svg}>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user