Merge branch 'master' of gitlab.cip.ifi.lmu.de:jost/UniWorX into feat/exercises
This commit is contained in:
commit
584d711cbc
@ -1,3 +1,6 @@
|
|||||||
|
* Version 10.07.2018
|
||||||
|
Bugfixes, wählbares Format für Datum
|
||||||
|
|
||||||
* Version 04.07.2018
|
* Version 04.07.2018
|
||||||
|
|
||||||
Hinweis eingefügt, dass alle Daten des Systems spätestens im Dezember 2018
|
Hinweis eingefügt, dass alle Daten des Systems spätestens im Dezember 2018
|
||||||
|
|||||||
@ -40,7 +40,11 @@ ldap:
|
|||||||
password: "_env:LDAPPW:"
|
password: "_env:LDAPPW:"
|
||||||
basename: "_env:LDAPBN:"
|
basename: "_env:LDAPBN:"
|
||||||
|
|
||||||
userDefaultFavourites: 12
|
default-favourites: 12
|
||||||
|
default-theme: Default
|
||||||
|
default-date-time-format: "%a %d %b %Y %R"
|
||||||
|
default-date-format: "%d.%m.%Y"
|
||||||
|
default-time-format: "%R"
|
||||||
|
|
||||||
cryptoid-keyfile: "_env:CRYPTOID_KEYFILE:cryptoid_key.bf"
|
cryptoid-keyfile: "_env:CRYPTOID_KEYFILE:cryptoid_key.bf"
|
||||||
|
|
||||||
|
|||||||
19
db.hs
19
db.hs
@ -5,6 +5,7 @@
|
|||||||
{-# LANGUAGE PackageImports #-}
|
{-# LANGUAGE PackageImports #-}
|
||||||
{-# LANGUAGE NoImplicitPrelude #-}
|
{-# LANGUAGE NoImplicitPrelude #-}
|
||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
|
||||||
import "uniworx" Import hiding (Option(..))
|
import "uniworx" Import hiding (Option(..))
|
||||||
import "uniworx" Application (db, getAppDevSettings)
|
import "uniworx" Application (db, getAppDevSettings)
|
||||||
@ -47,7 +48,7 @@ main = do
|
|||||||
|
|
||||||
fillDb :: DB ()
|
fillDb :: DB ()
|
||||||
fillDb = do
|
fillDb = do
|
||||||
defaultFavourites <- getsYesod $ appDefaultFavourites . appSettings
|
AppSettings{..} <- getsYesod appSettings
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
let
|
let
|
||||||
summer2017 = TermIdentifier 2017 Summer
|
summer2017 = TermIdentifier 2017 Summer
|
||||||
@ -60,7 +61,10 @@ fillDb = do
|
|||||||
, userEmail = "G.Kleen@campus.lmu.de"
|
, userEmail = "G.Kleen@campus.lmu.de"
|
||||||
, userDisplayName = "Gregor Kleen"
|
, userDisplayName = "Gregor Kleen"
|
||||||
, userMaxFavourites = 6
|
, userMaxFavourites = 6
|
||||||
, userTheme = AberdeenReds
|
, userTheme = Default
|
||||||
|
, userDateTimeFormat = appDefaultDateTimeFormat
|
||||||
|
, userDateFormat = appDefaultDateFormat
|
||||||
|
, userTimeFormat = appDefaultTimeFormat
|
||||||
}
|
}
|
||||||
fhamann <- insert User
|
fhamann <- insert User
|
||||||
{ userPlugin = "LDAP"
|
{ userPlugin = "LDAP"
|
||||||
@ -68,8 +72,11 @@ fillDb = do
|
|||||||
, userMatrikelnummer = Nothing
|
, userMatrikelnummer = Nothing
|
||||||
, userEmail = "felix.hamann@campus.lmu.de"
|
, userEmail = "felix.hamann@campus.lmu.de"
|
||||||
, userDisplayName = "Felix Hamann"
|
, userDisplayName = "Felix Hamann"
|
||||||
, userMaxFavourites = defaultFavourites
|
, userMaxFavourites = appDefaultMaxFavourites
|
||||||
, userTheme = Default
|
, userTheme = Default
|
||||||
|
, userDateTimeFormat = appDefaultDateTimeFormat
|
||||||
|
, userDateFormat = appDefaultDateFormat
|
||||||
|
, userTimeFormat = appDefaultTimeFormat
|
||||||
}
|
}
|
||||||
jost <- insert User
|
jost <- insert User
|
||||||
{ userPlugin = "LDAP"
|
{ userPlugin = "LDAP"
|
||||||
@ -79,6 +86,9 @@ fillDb = do
|
|||||||
, userDisplayName = "Steffen Jost"
|
, userDisplayName = "Steffen Jost"
|
||||||
, userMaxFavourites = 14
|
, userMaxFavourites = 14
|
||||||
, userTheme = MossGreen
|
, userTheme = MossGreen
|
||||||
|
, userDateTimeFormat = appDefaultDateTimeFormat
|
||||||
|
, userDateFormat = appDefaultDateFormat
|
||||||
|
, userTimeFormat = appDefaultTimeFormat
|
||||||
}
|
}
|
||||||
void . insert $ User
|
void . insert $ User
|
||||||
{ userPlugin = "LDAP"
|
{ userPlugin = "LDAP"
|
||||||
@ -88,6 +98,9 @@ fillDb = do
|
|||||||
, userDisplayName = "Max Musterstudent"
|
, userDisplayName = "Max Musterstudent"
|
||||||
, userMaxFavourites = 7
|
, userMaxFavourites = 7
|
||||||
, userTheme = AberdeenReds
|
, userTheme = AberdeenReds
|
||||||
|
, userDateTimeFormat = appDefaultDateTimeFormat
|
||||||
|
, userDateFormat = appDefaultDateFormat
|
||||||
|
, userTimeFormat = appDefaultTimeFormat
|
||||||
}
|
}
|
||||||
void . insert $ Term
|
void . insert $ Term
|
||||||
{ termName = summer2017
|
{ termName = summer2017
|
||||||
|
|||||||
@ -76,6 +76,7 @@ CorrectionHead tid@TermId courseShortHand@Text sheetName@Text cid@CryptoFileName
|
|||||||
SubmissionMember g@Int: Mitabgebende(r) ##{display g}
|
SubmissionMember g@Int: Mitabgebende(r) ##{display g}
|
||||||
SubmissionArchive: Zip-Archiv der Abgabedatei(en)
|
SubmissionArchive: Zip-Archiv der Abgabedatei(en)
|
||||||
SubmissionFile: Datei zur Abgabe
|
SubmissionFile: Datei zur Abgabe
|
||||||
|
SubmissionFiles: Abgegebene Dateien
|
||||||
SubmissionAlreadyExistsFor user@Text: #{user} hat bereits eine Abgabe zu diesem bÜbungsblatt.
|
SubmissionAlreadyExistsFor user@Text: #{user} hat bereits eine Abgabe zu diesem bÜbungsblatt.
|
||||||
|
|
||||||
CorrectionsTitle: Zugewiesene Korrekturen
|
CorrectionsTitle: Zugewiesene Korrekturen
|
||||||
@ -195,4 +196,14 @@ NoUpcomingSheetDeadlines: Keine anstehenden Übungsblätter
|
|||||||
|
|
||||||
AdminFor: Administrator
|
AdminFor: Administrator
|
||||||
LecturerFor: Dozent
|
LecturerFor: Dozent
|
||||||
UserListTitle: Komprehensive Benutzerliste
|
UserListTitle: Komprehensive Benutzerliste
|
||||||
|
|
||||||
|
DateTimeFormat: Datums- und Uhrzeitformat
|
||||||
|
DateFormat: Datumsformat
|
||||||
|
TimeFormat: Uhrzeitformat
|
||||||
|
|
||||||
|
InvalidDateTimeFormat: Ungültiges Datums- und Zeitformat, JJJJ-MM-TTTHH:MM[:SS] Format erwartet
|
||||||
|
AmbiguousUTCTime: Der angegebene Zeitpunkt lässt sich nicht eindeutig zu UTC konvertieren
|
||||||
|
|
||||||
|
LastEdits: Letzte Änderungen
|
||||||
|
EditedBy name@Text time@Text: Durch #{name} um #{time}
|
||||||
9
models
9
models
@ -4,8 +4,11 @@ User json
|
|||||||
matrikelnummer Text Maybe
|
matrikelnummer Text Maybe
|
||||||
email Text
|
email Text
|
||||||
displayName Text
|
displayName Text
|
||||||
maxFavourites Int default=12
|
maxFavourites Int default=12
|
||||||
theme Theme default='default'
|
theme Theme default='Default'
|
||||||
|
dateTimeFormat DateTimeFormat "default='%a %d %b %Y %R'"
|
||||||
|
dateFormat DateTimeFormat "default='%d.%m.%Y'"
|
||||||
|
timeFormat DateTimeFormat "default='%R'"
|
||||||
UniqueAuthentication plugin ident
|
UniqueAuthentication plugin ident
|
||||||
UniqueEmail email
|
UniqueEmail email
|
||||||
deriving Show
|
deriving Show
|
||||||
@ -67,7 +70,7 @@ Course
|
|||||||
registerTo UTCTime Maybe
|
registerTo UTCTime Maybe
|
||||||
deregisterUntil UTCTime Maybe
|
deregisterUntil UTCTime Maybe
|
||||||
registerSecret Text Maybe -- Falls ein Passwort erforderlich ist
|
registerSecret Text Maybe -- Falls ein Passwort erforderlich ist
|
||||||
materialFree Bool default=true
|
materialFree Bool
|
||||||
CourseTermShort term shorthand
|
CourseTermShort term shorthand
|
||||||
CourseEdit
|
CourseEdit
|
||||||
user UserId
|
user UserId
|
||||||
|
|||||||
@ -83,6 +83,9 @@ dependencies:
|
|||||||
- MonadRandom
|
- MonadRandom
|
||||||
- email-validate
|
- email-validate
|
||||||
- scientific
|
- scientific
|
||||||
|
- tz
|
||||||
|
- system-locale
|
||||||
|
- th-lift-instances
|
||||||
|
|
||||||
# The library contains all of our application code. The executable
|
# The library contains all of our application code. The executable
|
||||||
# defined below is just a thin wrapper.
|
# defined below is just a thin wrapper.
|
||||||
|
|||||||
4
routes
4
routes
@ -64,10 +64,10 @@
|
|||||||
/subs SSubsR GET POST
|
/subs SSubsR GET POST
|
||||||
/subs/new SubmissionNewR GET POST !timeANDregistered
|
/subs/new SubmissionNewR GET POST !timeANDregistered
|
||||||
/subs/own SubmissionOwnR GET !free -- just redirect
|
/subs/own SubmissionOwnR GET !free -- just redirect
|
||||||
/sub/#CryptoFileNameSubmission SubmissionR !corrector:
|
/sub/#CryptoFileNameSubmission SubmissionR !correctorANDisRead:
|
||||||
/ SubShowR GET POST !ownerANDtime !ownerANDisRead
|
/ SubShowR GET POST !ownerANDtime !ownerANDisRead
|
||||||
/archive/#{ZIPArchiveName SubmissionFileType} SubArchiveR GET !owner
|
/archive/#{ZIPArchiveName SubmissionFileType} SubArchiveR GET !owner
|
||||||
/correction CorrectionR GET POST !ownerANDisRead
|
/correction CorrectionR GET POST !corrector !ownerANDisRead
|
||||||
!/#SubmissionFileType/*FilePath SubDownloadR GET !owner
|
!/#SubmissionFileType/*FilePath SubDownloadR GET !owner
|
||||||
/correctors SCorrR GET POST
|
/correctors SCorrR GET POST
|
||||||
!/#SheetFileType/*FilePath SFileR GET !timeANDregistered !timeANDmaterials !corrector
|
!/#SheetFileType/*FilePath SFileR GET !timeANDregistered !timeANDmaterials !corrector
|
||||||
|
|||||||
@ -72,8 +72,8 @@ import System.FilePath
|
|||||||
|
|
||||||
import Handler.Utils.Templates
|
import Handler.Utils.Templates
|
||||||
import Handler.Utils.StudyFeatures
|
import Handler.Utils.StudyFeatures
|
||||||
import Handler.Utils.DateTime
|
|
||||||
import Control.Lens
|
import Control.Lens
|
||||||
|
import Utils
|
||||||
import Utils.Lens
|
import Utils.Lens
|
||||||
|
|
||||||
import Data.Aeson
|
import Data.Aeson
|
||||||
@ -88,9 +88,6 @@ import Text.Shakespeare.Text (st)
|
|||||||
instance DisplayAble TermId where
|
instance DisplayAble TermId where
|
||||||
display = termToText . unTermKey
|
display = termToText . unTermKey
|
||||||
|
|
||||||
instance DisplayAble UTCTime where
|
|
||||||
display = pack . formatTimeGerDT2 -- default Time Format to be used: 00.00.00 00:00
|
|
||||||
|
|
||||||
instance (PathPiece b) => DisplayAble (Data.CryptoID.CryptoID a b) where
|
instance (PathPiece b) => DisplayAble (Data.CryptoID.CryptoID a b) where
|
||||||
display = toPathPiece -- requires import of Data.CryptoID here
|
display = toPathPiece -- requires import of Data.CryptoID here
|
||||||
-- -- MOVE ABOVE
|
-- -- MOVE ABOVE
|
||||||
@ -184,6 +181,13 @@ instance RenderMessage UniWorX SheetFileType where
|
|||||||
SheetMarking -> renderMessage' MsgSheetMarking
|
SheetMarking -> renderMessage' MsgSheetMarking
|
||||||
where renderMessage' = renderMessage foundation ls
|
where renderMessage' = renderMessage foundation ls
|
||||||
|
|
||||||
|
getTimeLocale' :: [Lang] -> TimeLocale
|
||||||
|
getTimeLocale' = $(timeLocaleMap [("de", "de_DE.utf8")])
|
||||||
|
|
||||||
|
appTZ :: TZ
|
||||||
|
appTZ = $(includeSystemTZ "Europe/Berlin")
|
||||||
|
|
||||||
|
|
||||||
-- Access Control
|
-- Access Control
|
||||||
data AccessPredicate
|
data AccessPredicate
|
||||||
= APPure (Route UniWorX -> Bool -> Reader MsgRenderer AuthResult)
|
= APPure (Route UniWorX -> Bool -> Reader MsgRenderer AuthResult)
|
||||||
@ -600,7 +604,7 @@ instance YesodBreadcrumbs UniWorX where
|
|||||||
breadcrumb UsersR = return ("Benutzer", Just HomeR)
|
breadcrumb UsersR = return ("Benutzer", Just HomeR)
|
||||||
breadcrumb AdminTestR = return ("Test" , Just HomeR)
|
breadcrumb AdminTestR = return ("Test" , Just HomeR)
|
||||||
breadcrumb (AdminUserR _) = return ("Users" , Just UsersR)
|
breadcrumb (AdminUserR _) = return ("Users" , Just UsersR)
|
||||||
breadcrumb VersionR = return ("Info" , Just HomeR)
|
breadcrumb VersionR = return ("Impressum" , Just HomeR)
|
||||||
|
|
||||||
breadcrumb ProfileR = return ("Profile" , Just HomeR)
|
breadcrumb ProfileR = return ("Profile" , Just HomeR)
|
||||||
breadcrumb ProfileDataR = return ("Data" , Just ProfileR)
|
breadcrumb ProfileDataR = return ("Data" , Just ProfileR)
|
||||||
@ -999,9 +1003,14 @@ instance YesodAuth UniWorX where
|
|||||||
userEmail <- maybe (throwError $ ServerError "Could not retrieve user email") return userEmail'
|
userEmail <- maybe (throwError $ ServerError "Could not retrieve user email") return userEmail'
|
||||||
userDisplayName <- maybe (throwError $ ServerError "Could not retrieve user name") return userDisplayName'
|
userDisplayName <- maybe (throwError $ ServerError "Could not retrieve user name") return userDisplayName'
|
||||||
|
|
||||||
|
AppSettings{..} <- getsYesod appSettings
|
||||||
|
|
||||||
let
|
let
|
||||||
userMaxFavourites = 12 -- TODO: appDefaultFavourites appSettings
|
userMaxFavourites = appDefaultMaxFavourites
|
||||||
userTheme = Default -- TODO: appDefaultFavourites appSettings
|
userTheme = appDefaultTheme
|
||||||
|
userDateTimeFormat = appDefaultDateTimeFormat
|
||||||
|
userDateFormat = appDefaultDateFormat
|
||||||
|
userTimeFormat = appDefaultTimeFormat
|
||||||
newUser = User{..}
|
newUser = User{..}
|
||||||
userUpdate = [ UserMatrikelnummer =. userMatrikelnummer
|
userUpdate = [ UserMatrikelnummer =. userMatrikelnummer
|
||||||
, UserDisplayName =. userDisplayName
|
, UserDisplayName =. userDisplayName
|
||||||
|
|||||||
@ -62,8 +62,8 @@ getTermCourseListR tid = do
|
|||||||
[ sortable (Just "shorthand") (textCell MsgCourse) $ anchorCell'
|
[ sortable (Just "shorthand") (textCell MsgCourse) $ anchorCell'
|
||||||
(\(Entity _ Course{..}, _) -> CourseR courseTerm courseShorthand CShowR)
|
(\(Entity _ Course{..}, _) -> CourseR courseTerm courseShorthand CShowR)
|
||||||
(\(Entity _ Course{..}, _) -> toWidget courseShorthand)
|
(\(Entity _ Course{..}, _) -> toWidget courseShorthand)
|
||||||
, sortable (Just "register-from") (textCell MsgRegisterFrom) $ \(Entity _ Course{..}, _) -> textCell $ display courseRegisterFrom
|
, sortable (Just "register-from") (textCell MsgRegisterFrom) $ \(Entity _ Course{..}, _) -> cell $ traverse (formatTime SelFormatDateTime) courseRegisterFrom >>= maybe mempty toWidget
|
||||||
, sortable (Just "register-to") (textCell MsgRegisterTo) $ \(Entity _ Course{..}, _) -> textCell $ display courseRegisterTo
|
, sortable (Just "register-to") (textCell MsgRegisterTo) $ \(Entity _ Course{..}, _) -> cell $ traverse (formatTime SelFormatDateTime) courseRegisterTo >>= maybe mempty toWidget
|
||||||
, sortable (Just "members") (textCell MsgCourseMembers) $ \(Entity _ Course{..}, E.Value num) -> textCell $ case courseCapacity of
|
, sortable (Just "members") (textCell MsgCourseMembers) $ \(Entity _ Course{..}, E.Value num) -> textCell $ case courseCapacity of
|
||||||
Nothing -> MsgCourseMembersCount num
|
Nothing -> MsgCourseMembersCount num
|
||||||
Just max -> MsgCourseMembersCountLimited num max
|
Just max -> MsgCourseMembersCountLimited num max
|
||||||
@ -110,6 +110,8 @@ getCShowR tid csh = do
|
|||||||
let course = entityVal courseEnt
|
let course = entityVal courseEnt
|
||||||
(regWidget, regEnctype) <- generateFormPost $ identifyForm "registerBtn" $ registerForm registered $ courseRegisterSecret course
|
(regWidget, regEnctype) <- generateFormPost $ identifyForm "registerBtn" $ registerForm registered $ courseRegisterSecret course
|
||||||
registrationOpen <- (==Authorized) <$> isAuthorized (CourseR tid csh CRegisterR) True
|
registrationOpen <- (==Authorized) <$> isAuthorized (CourseR tid csh CRegisterR) True
|
||||||
|
mRegFrom <- traverse (formatTime SelFormatDateTime) $ courseRegisterFrom course
|
||||||
|
mRegTo <- traverse (formatTime SelFormatDateTime) $ courseRegisterTo course
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitle $ [shamlet| #{toPathPiece tid} - #{csh}|]
|
setTitle $ [shamlet| #{toPathPiece tid} - #{csh}|]
|
||||||
$(widgetFile "course")
|
$(widgetFile "course")
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import Handler.Utils
|
|||||||
|
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
import Data.Time
|
import Data.Time hiding (formatTime)
|
||||||
-- import qualified Data.Text as T
|
-- import qualified Data.Text as T
|
||||||
-- import Yesod.Form.Bootstrap3
|
-- import Yesod.Form.Bootstrap3
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ homeAnonymous = do
|
|||||||
, sortable (Just "term") (textCell MsgTerm) $ \DBRow{ dbrOutput=(Entity {entityVal = course}) } ->
|
, sortable (Just "term") (textCell MsgTerm) $ \DBRow{ dbrOutput=(Entity {entityVal = course}) } ->
|
||||||
textCell $ display $ courseTerm course
|
textCell $ display $ courseTerm course
|
||||||
, sortable (Just "deadline") (textCell MsgRegisterTo) $ \DBRow{ dbrOutput=(Entity {entityVal = course}) } ->
|
, sortable (Just "deadline") (textCell MsgRegisterTo) $ \DBRow{ dbrOutput=(Entity {entityVal = course}) } ->
|
||||||
textCell $ display $ courseRegisterTo course
|
cell $ traverse (formatTime SelFormatDateTime) (courseRegisterTo course) >>= maybe mempty toWidget
|
||||||
]
|
]
|
||||||
courseTable <- dbTable def $ DBTable
|
courseTable <- dbTable def $ DBTable
|
||||||
{ dbtSQLQuery = tableData
|
{ dbtSQLQuery = tableData
|
||||||
@ -150,7 +150,7 @@ homeUser uid = do
|
|||||||
, sortable (Just "sheet") (textCell MsgSheet) $ \DBRow{ dbrOutput=(E.Value tid, E.Value csh, E.Value shn, _, _) } ->
|
, sortable (Just "sheet") (textCell MsgSheet) $ \DBRow{ dbrOutput=(E.Value tid, E.Value csh, E.Value shn, _, _) } ->
|
||||||
cell [whamlet|<a href=@{CSheetR tid csh shn SShowR}>#{display shn}|]
|
cell [whamlet|<a href=@{CSheetR tid csh shn SShowR}>#{display shn}|]
|
||||||
, sortable (Just "deadline") (textCell MsgDeadline) $ \DBRow{ dbrOutput=(_, _, _, E.Value deadline, _) } ->
|
, sortable (Just "deadline") (textCell MsgDeadline) $ \DBRow{ dbrOutput=(_, _, _, E.Value deadline, _) } ->
|
||||||
textCell $ display deadline
|
cell $ formatTime SelFormatDateTime deadline >>= toWidget
|
||||||
, sortable (Just "done") (textCell MsgDone) $ \(DBRow{ dbrOutput=(E.Value tid, E.Value csh, E.Value shn, _, E.Value mbsid) }) ->
|
, sortable (Just "done") (textCell MsgDone) $ \(DBRow{ dbrOutput=(E.Value tid, E.Value csh, E.Value shn, _, E.Value mbsid) }) ->
|
||||||
case mbsid of
|
case mbsid of
|
||||||
Nothing -> mempty
|
Nothing -> mempty
|
||||||
|
|||||||
@ -22,6 +22,9 @@ import Database.Esqueleto ((^.))
|
|||||||
data SettingsForm = SettingsForm
|
data SettingsForm = SettingsForm
|
||||||
{ stgMaxFavourties :: Int
|
{ stgMaxFavourties :: Int
|
||||||
, stgTheme :: Theme
|
, stgTheme :: Theme
|
||||||
|
, stgDateTime :: DateTimeFormat
|
||||||
|
, stgDate :: DateTimeFormat
|
||||||
|
, stgTime :: DateTimeFormat
|
||||||
}
|
}
|
||||||
|
|
||||||
makeSettingForm :: Maybe SettingsForm -> Form SettingsForm
|
makeSettingForm :: Maybe SettingsForm -> Form SettingsForm
|
||||||
@ -32,6 +35,9 @@ makeSettingForm template = identForm FIDsettings $ \html -> do
|
|||||||
(fslpI MsgFavoriten "Anzahl Favoriten") (stgMaxFavourties <$> template)
|
(fslpI MsgFavoriten "Anzahl Favoriten") (stgMaxFavourties <$> template)
|
||||||
<*> areq (selectFieldList themeList)
|
<*> areq (selectFieldList themeList)
|
||||||
(fslpI MsgTheme "theme-select" ) (stgTheme <$> template) -- TODO: pass theme-select as id-attribute or similar.
|
(fslpI MsgTheme "theme-select" ) (stgTheme <$> template) -- TODO: pass theme-select as id-attribute or similar.
|
||||||
|
<*> areq (selectField $ dateTimeFormatOptions SelFormatDateTime) (fslI MsgDateTimeFormat) (stgDateTime <$> template)
|
||||||
|
<*> areq (selectField $ dateTimeFormatOptions SelFormatDate) (fslI MsgDateFormat) (stgDate <$> template)
|
||||||
|
<*> areq (selectField $ dateTimeFormatOptions SelFormatTime) (fslI MsgTimeFormat) (stgTime <$> template)
|
||||||
<* submitButton
|
<* submitButton
|
||||||
return (result, widget) -- no validation required here
|
return (result, widget) -- no validation required here
|
||||||
|
|
||||||
@ -43,13 +49,19 @@ getProfileR = do
|
|||||||
let settingsTemplate = Just $ SettingsForm
|
let settingsTemplate = Just $ SettingsForm
|
||||||
{ stgMaxFavourties = userMaxFavourites
|
{ stgMaxFavourties = userMaxFavourites
|
||||||
, stgTheme = userTheme
|
, stgTheme = userTheme
|
||||||
|
, stgDateTime = userDateTimeFormat
|
||||||
|
, stgDate = userDateFormat
|
||||||
|
, stgTime = userTimeFormat
|
||||||
}
|
}
|
||||||
((res,formWidget), formEnctype) <- runFormPost $ makeSettingForm settingsTemplate
|
((res,formWidget), formEnctype) <- runFormPost $ makeSettingForm settingsTemplate
|
||||||
case res of
|
case res of
|
||||||
(FormSuccess SettingsForm{..}) -> do
|
(FormSuccess SettingsForm{..}) -> do
|
||||||
runDB $ do
|
runDB $ do
|
||||||
update uid [ UserMaxFavourites =. stgMaxFavourties
|
update uid [ UserMaxFavourites =. stgMaxFavourties
|
||||||
, UserTheme =. stgTheme
|
, UserTheme =. stgTheme
|
||||||
|
, UserDateTimeFormat =. stgDateTime
|
||||||
|
, UserDateFormat =. stgDate
|
||||||
|
, UserTimeFormat =. stgTime
|
||||||
]
|
]
|
||||||
when (stgMaxFavourties < userMaxFavourites) $ do
|
when (stgMaxFavourties < userMaxFavourites) $ do
|
||||||
-- prune Favourites to user-defined size
|
-- prune Favourites to user-defined size
|
||||||
|
|||||||
@ -165,8 +165,8 @@ getSheetListR tid csh = do
|
|||||||
let tid = courseTerm course
|
let tid = courseTerm course
|
||||||
let colBase = mconcat
|
let colBase = mconcat
|
||||||
[ headed "Blatt" $ \(sid,sheet,_) -> simpleLink (toWgt $ sheetName sheet) $ CSheetR tid csh (sheetName sheet) SShowR
|
[ headed "Blatt" $ \(sid,sheet,_) -> simpleLink (toWgt $ sheetName sheet) $ CSheetR tid csh (sheetName sheet) SShowR
|
||||||
, headed "Abgabe ab" $ toWgt . formatTimeGerWD . sheetActiveFrom . snd3
|
, headed "Abgabe ab" $ \(_,Sheet{..},_) -> formatTime SelFormatDateTime sheetActiveFrom >>= toWidget
|
||||||
, headed "Abgabe bis" $ toWgt . formatTimeGerWD . sheetActiveTo . snd3
|
, headed "Abgabe bis" $ \(_,Sheet{..},_) -> formatTime SelFormatDateTime sheetActiveTo >>= toWidget
|
||||||
, headed "Bewertung" $ toWgt . display . sheetType . snd3
|
, headed "Bewertung" $ toWgt . display . sheetType . snd3
|
||||||
]
|
]
|
||||||
let colAdmin = mconcat -- only show edit button for allowed course assistants
|
let colAdmin = mconcat -- only show edit button for allowed course assistants
|
||||||
@ -227,7 +227,7 @@ getSShowR tid csh shn = do
|
|||||||
SheetExercise -> textCell $ display $ sheetActiveFrom sheet
|
SheetExercise -> textCell $ display $ sheetActiveFrom sheet
|
||||||
SheetHint -> textCell $ display $ sheetHintFrom sheet
|
SheetHint -> textCell $ display $ sheetHintFrom sheet
|
||||||
SheetSolution -> textCell $ display $ sheetSolutionFrom sheet
|
SheetSolution -> textCell $ display $ sheetSolutionFrom sheet
|
||||||
, sortable (Just "time") "Modifikation" $ \(_,E.Value modified,_) -> stringCell $ formatTimeGerWDT (modified :: UTCTime)
|
, sortable (Just "time") "Modifikation" $ \(_,E.Value modified,_) -> cell $ formatTime SelFormatDateTime (modified :: UTCTime) >>= toWidget
|
||||||
]
|
]
|
||||||
fileTable <- dbTable def $ DBTable
|
fileTable <- dbTable def $ DBTable
|
||||||
{ dbtSQLQuery = fileData
|
{ dbtSQLQuery = fileData
|
||||||
@ -249,8 +249,9 @@ getSShowR tid csh shn = do
|
|||||||
}
|
}
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
setTitle $ toHtml $ T.append "Übung " $ sheetName sheet
|
setTitle $ toHtml $ T.append "Übung " $ sheetName sheet
|
||||||
|
sheetFrom <- formatTime SelFormatDateTime $ sheetActiveFrom sheet
|
||||||
|
sheetTo <- formatTime SelFormatDateTime $ sheetActiveTo sheet
|
||||||
$(widgetFile "sheetShow")
|
$(widgetFile "sheetShow")
|
||||||
[whamlet| Under Construction !!! |] -- TODO
|
|
||||||
|
|
||||||
getSFileR :: TermId -> Text -> Text -> SheetFileType -> FilePath -> Handler TypedContent
|
getSFileR :: TermId -> Text -> Text -> SheetFileType -> FilePath -> Handler TypedContent
|
||||||
getSFileR tid csh shn typ title = do
|
getSFileR tid csh shn typ title = do
|
||||||
|
|||||||
@ -160,7 +160,7 @@ submissionHelper tid csh shn (SubmissionMode mcid) = do
|
|||||||
E.orderBy [E.desc $ submissionEdit E.^. SubmissionEditTime]
|
E.orderBy [E.desc $ submissionEdit E.^. SubmissionEditTime]
|
||||||
E.limit numberOfSubmissionEditDates
|
E.limit numberOfSubmissionEditDates
|
||||||
return $ (user E.^. UserDisplayName, submissionEdit E.^. SubmissionEditTime)
|
return $ (user E.^. UserDisplayName, submissionEdit E.^. SubmissionEditTime)
|
||||||
let lastEdits = map (bimap E.unValue E.unValue) lastEditValues
|
lastEdits <- forM lastEditValues $ \(E.Value name, E.Value time) -> (name, ) <$> formatTime SelFormatDateTime time
|
||||||
return (sheet,buddies,lastEdits)
|
return (sheet,buddies,lastEdits)
|
||||||
let unpackZips = True -- undefined -- TODO
|
let unpackZips = True -- undefined -- TODO
|
||||||
((res,formWidget), formEnctype) <- runFormPost $ makeSubmissionForm msmid unpackZips sheetGrouping $ map E.unValue buddies
|
((res,formWidget), formEnctype) <- runFormPost $ makeSubmissionForm msmid unpackZips sheetGrouping $ map E.unValue buddies
|
||||||
@ -246,6 +246,8 @@ submissionHelper tid csh shn (SubmissionMode mcid) = do
|
|||||||
Nothing -> return ()
|
Nothing -> return ()
|
||||||
|
|
||||||
actionUrl <- Data.Maybe.fromJust <$> getCurrentRoute
|
actionUrl <- Data.Maybe.fromJust <$> getCurrentRoute
|
||||||
|
maySubmit <- (== Authorized) <$> isAuthorized actionUrl True
|
||||||
|
|
||||||
-- Maybe construct a table to display uploaded archive files
|
-- Maybe construct a table to display uploaded archive files
|
||||||
let colonnadeFiles :: _ -> Colonnade Sortable _ (DBCell (WidgetT UniWorX IO) ())
|
let colonnadeFiles :: _ -> Colonnade Sortable _ (DBCell (WidgetT UniWorX IO) ())
|
||||||
colonnadeFiles cid = mconcat
|
colonnadeFiles cid = mconcat
|
||||||
@ -355,7 +357,8 @@ getSubArchiveR tid csh shn cID@CryptoID{..} (ZIPArchiveName sfType) = do
|
|||||||
|
|
||||||
fileSource' = do
|
fileSource' = do
|
||||||
fileSource .| Conduit.map entityVal
|
fileSource .| Conduit.map entityVal
|
||||||
maybe (return ()) (yieldM . ratingFile cID) rating
|
when (sfType == SubmissionCorrected) $
|
||||||
|
maybe (return ()) (yieldM . ratingFile cID) rating
|
||||||
|
|
||||||
zipComment = Text.encodeUtf8 . pack $ CI.foldedCase ciphertext
|
zipComment = Text.encodeUtf8 . pack $ CI.foldedCase ciphertext
|
||||||
|
|
||||||
|
|||||||
@ -46,19 +46,25 @@ getTermShowR = do
|
|||||||
anchorCell' (\(Entity tid _, _) -> TermCourseListR tid)
|
anchorCell' (\(Entity tid _, _) -> TermCourseListR tid)
|
||||||
(\(Entity tid _, _) -> [whamlet|#{display tid}|])
|
(\(Entity tid _, _) -> [whamlet|#{display tid}|])
|
||||||
, sortable (Just "lecture-start") (i18nCell MsgLectureStart) $ \(Entity _ Term{..},_) ->
|
, sortable (Just "lecture-start") (i18nCell MsgLectureStart) $ \(Entity _ Term{..},_) ->
|
||||||
stringCell $ formatTimeGerWD termLectureStart
|
cell $ formatTime SelFormatDate termLectureStart >>= toWidget
|
||||||
, sortable (Just "lecture-end") "Ende Vorlesungen" $ \(Entity _ Term{..},_) ->
|
, sortable (Just "lecture-end") "Ende Vorlesungen" $ \(Entity _ Term{..},_) ->
|
||||||
stringCell $ formatTimeGerWD termLectureEnd
|
cell $ formatTime SelFormatDate termLectureEnd >>= toWidget
|
||||||
, sortable Nothing "Aktiv" $ \(Entity _ Term{..},_) ->
|
, sortable Nothing "Aktiv" $ \(Entity _ Term{..},_) ->
|
||||||
textCell $ (bool "" tickmark termActive :: Text)
|
textCell $ (bool "" tickmark termActive :: Text)
|
||||||
, sortable Nothing "Kurse" $ \(_, E.Value numCourses) ->
|
, sortable Nothing "Kurse" $ \(_, E.Value numCourses) ->
|
||||||
cell [whamlet|_{MsgNumCourses numCourses}|]
|
cell [whamlet|_{MsgNumCourses numCourses}|]
|
||||||
, sortable (Just "start") "Semesteranfang" $ \(Entity _ Term{..},_) ->
|
, sortable (Just "start") "Semesteranfang" $ \(Entity _ Term{..},_) ->
|
||||||
stringCell $ formatTimeGerWD termStart
|
cell $ formatTime SelFormatDate termStart >>= toWidget
|
||||||
, sortable (Just "end") "Semesterende" $ \(Entity _ Term{..},_) ->
|
, sortable (Just "end") "Semesterende" $ \(Entity _ Term{..},_) ->
|
||||||
stringCell $ formatTimeGerWD termEnd
|
cell $ formatTime SelFormatDate termEnd >>= toWidget
|
||||||
, sortable Nothing "Feiertage im Semester" $ \(Entity _ Term{..},_) ->
|
, sortable Nothing "Feiertage im Semester" $ \(Entity _ Term{..},_) ->
|
||||||
stringCell $ (intercalate ", ") $ map formatTimeGerWD termHolidays
|
cell $ do
|
||||||
|
termHolidays' <- mapM (formatTime SelFormatDate) termHolidays
|
||||||
|
[whamlet|
|
||||||
|
<ul .list--inline .list--comma-separated>
|
||||||
|
$forall holiday <- termHolidays'
|
||||||
|
<li>#{holiday}
|
||||||
|
|]
|
||||||
]
|
]
|
||||||
-- let adminColonnade =
|
-- let adminColonnade =
|
||||||
-- [ sortable Nothing "Edit" $ \(Entity tid Term{..},_) -> cell $ do
|
-- [ sortable Nothing "Edit" $ \(Entity tid Term{..},_) -> cell $ do
|
||||||
|
|||||||
@ -34,6 +34,12 @@ getUsersR = do
|
|||||||
, sortable (Just "display-name") (i18nCell MsgName) $ \DBRow{ dbrOutput = Entity uid User{..} } -> anchorCellM
|
, sortable (Just "display-name") (i18nCell MsgName) $ \DBRow{ dbrOutput = Entity uid User{..} } -> anchorCellM
|
||||||
(AdminUserR <$> encrypt uid)
|
(AdminUserR <$> encrypt uid)
|
||||||
(toWidget . display $ userDisplayName)
|
(toWidget . display $ userDisplayName)
|
||||||
|
, sortable (Just "matriculation") (i18nCell MsgMatrikelNr) $ \DBRow{ dbrOutput = Entity uid User{..} } -> anchorCellM
|
||||||
|
(AdminUserR <$> encrypt uid)
|
||||||
|
(toWidget . display $ userMatrikelnummer)
|
||||||
|
-- , sortable (Just "last-name") (i18nCell MsgName) $ \DBRow{ dbrOutput = Entity uid User{..} } -> anchorCellM
|
||||||
|
-- (AdminUserR <$> encrypt uid)
|
||||||
|
-- (toWidget . display $ last $ impureNonNull $ words $ userDisplayName)
|
||||||
, sortable Nothing (i18nCell MsgAdminFor) $ \DBRow{ dbrOutput = Entity uid _ } -> mempty
|
, sortable Nothing (i18nCell MsgAdminFor) $ \DBRow{ dbrOutput = Entity uid _ } -> mempty
|
||||||
{ dbCellContents = do
|
{ dbCellContents = do
|
||||||
schools <- E.select . E.from $ \(school `E.InnerJoin` userAdmin) -> do
|
schools <- E.select . E.from $ \(school `E.InnerJoin` userAdmin) -> do
|
||||||
@ -78,6 +84,12 @@ getUsersR = do
|
|||||||
[ ( "display-name"
|
[ ( "display-name"
|
||||||
, SortColumn $ \user -> user E.^. UserDisplayName
|
, SortColumn $ \user -> user E.^. UserDisplayName
|
||||||
)
|
)
|
||||||
|
, ( "matriculation"
|
||||||
|
, SortColumn $ \user -> user E.^. UserMatrikelnummer
|
||||||
|
)
|
||||||
|
-- , ( "last-name"
|
||||||
|
-- , SortColumn $ \user -> (last . impureNonNull . words) <$> (user E.^. UserDisplayName)
|
||||||
|
-- )
|
||||||
]
|
]
|
||||||
, dbtFilter = mempty
|
, dbtFilter = mempty
|
||||||
, dbtStyle = def
|
, dbtStyle = def
|
||||||
|
|||||||
@ -1,60 +1,127 @@
|
|||||||
module Handler.Utils.DateTime where
|
{-# LANGUAGE NoImplicitPrelude
|
||||||
|
, TemplateHaskell
|
||||||
|
, OverloadedStrings
|
||||||
|
, RecordWildCards
|
||||||
|
, TypeFamilies
|
||||||
|
#-}
|
||||||
|
|
||||||
import Data.Time
|
module Handler.Utils.DateTime
|
||||||
|
( utcToLocalTime
|
||||||
|
, localTimeToUTC, TZ.LocalToUTCResult(..)
|
||||||
|
, formatTime'
|
||||||
|
, formatTime
|
||||||
|
, getTimeLocale, getDateTimeFormat
|
||||||
|
, validDateTimeFormats, dateTimeFormatOptions
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
|
||||||
germanTimeLocale :: TimeLocale
|
import Data.Time.Zones hiding (localTimeToUTCFull)
|
||||||
germanTimeLocale = TimeLocale
|
import qualified Data.Time.Zones as TZ
|
||||||
{ wDays = [("Sonntag" ,"So")
|
|
||||||
,("Montag" ,"Mo")
|
|
||||||
,("Dienstag" ,"Di")
|
|
||||||
,("Mittwoch" ,"Mi")
|
|
||||||
,("Donnerstag" ,"Do")
|
|
||||||
,("Freitag" ,"Fr")
|
|
||||||
,("Samstag" ,"Sa")
|
|
||||||
]
|
|
||||||
, months = [("Januar" ,"Jan")
|
|
||||||
,("Februar" ,"Feb")
|
|
||||||
,("März" ,"Mär")
|
|
||||||
,("April" ,"Apr")
|
|
||||||
,("Mai" ,"Mai")
|
|
||||||
,("Juni" ,"Jun")
|
|
||||||
,("Juli" ,"Jul")
|
|
||||||
,("August" ,"Aug")
|
|
||||||
,("September" ,"Sep")
|
|
||||||
,("Oktober" ,"Okt")
|
|
||||||
,("November" ,"Nov")
|
|
||||||
,("Dezember" ,"Dez")
|
|
||||||
]
|
|
||||||
, amPm = ("am","pm")
|
|
||||||
, dateTimeFmt = "%a %e.%m.%y %k:%M"
|
|
||||||
, dateFmt = "%e.%m.%y"
|
|
||||||
, timeFmt = "%k:%M"
|
|
||||||
, time12Fmt = "%H:%M"
|
|
||||||
, knownTimeZones = [] -- TODO?
|
|
||||||
}
|
|
||||||
|
|
||||||
formatTimeGer :: FormatTime t => String -> t -> String
|
import Data.Time hiding (formatTime, localTimeToUTC, utcToLocalTime)
|
||||||
formatTimeGer = formatTime germanTimeLocale
|
import qualified Data.Time.Format as Time
|
||||||
|
|
||||||
formatTimeGerDTlong :: FormatTime t => t -> String
|
import Data.Set (Set)
|
||||||
formatTimeGerDTlong = formatTimeGer "%A, %e. %B %Y, %k:%M:%S"
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
formatTimeGerWDT :: FormatTime t => t -> String
|
utcToLocalTime :: UTCTime -> LocalTime
|
||||||
formatTimeGerWDT = formatTimeGer $ dateTimeFmt germanTimeLocale
|
utcToLocalTime = TZ.utcToLocalTimeTZ appTZ
|
||||||
|
|
||||||
formatTimeGerDT :: FormatTime t => t -> String -- 0.00.00 0:00
|
localTimeToUTC :: LocalTime -> LocalToUTCResult
|
||||||
formatTimeGerDT = formatTimeGer "%e.%m.%y %k:%M" -- leading spaces at start, otherwise 0 padding
|
localTimeToUTC = TZ.localTimeToUTCFull appTZ
|
||||||
|
|
||||||
-- the following is used by DisplayAble's display:
|
class FormatTime t => HasLocalTime t where
|
||||||
formatTimeGerDT2 :: FormatTime t => t -> String -- Day 00.00.00 00:00
|
toLocalTime :: t -> LocalTime
|
||||||
formatTimeGerDT2 = formatTimeGer "%a %d.%m.%y %H:%M" -- always padding with 0
|
|
||||||
|
|
||||||
formatTimeGerWD :: FormatTime t => t -> String
|
instance HasLocalTime LocalTime where
|
||||||
formatTimeGerWD = formatTimeGer "%a %e.%m.%y"
|
toLocalTime = id
|
||||||
|
|
||||||
formatTimeGerD :: FormatTime t => t -> String
|
instance HasLocalTime Day where
|
||||||
formatTimeGerD = formatTimeGer $ dateFmt germanTimeLocale
|
toLocalTime d = LocalTime d midnight
|
||||||
|
|
||||||
formatTimeGerT :: FormatTime t => t -> String
|
instance HasLocalTime UTCTime where
|
||||||
formatTimeGerT = formatTimeGer $ timeFmt germanTimeLocale
|
toLocalTime t = utcToLocalTime t
|
||||||
|
|
||||||
|
formatTime' :: (HasLocalTime t, MonadHandler m, HandlerSite m ~ UniWorX) => String -> t -> m Text
|
||||||
|
formatTime' fmtStr t = fmap fromString $ Time.formatTime <$> getTimeLocale <*> pure fmtStr <*> pure (toLocalTime t)
|
||||||
|
|
||||||
|
-- formatTime :: (FormatTime t, MonadHandler m, HandlerSite m ~ UniWorX, IsString str) => (DateTimeFormat -> String) -> t -> m str
|
||||||
|
-- Restricted type for safety
|
||||||
|
formatTime :: (HasLocalTime t, MonadHandler m, HandlerSite m ~ UniWorX) => SelDateTimeFormat -> t -> m Text
|
||||||
|
formatTime proj t = flip formatTime' t =<< (unDateTimeFormat <$> getDateTimeFormat proj)
|
||||||
|
|
||||||
|
getTimeLocale :: (MonadHandler m, HandlerSite m ~ UniWorX) => m TimeLocale
|
||||||
|
getTimeLocale = getTimeLocale' <$> languages
|
||||||
|
|
||||||
|
getDateTimeFormat :: (MonadHandler m, HandlerSite m ~ UniWorX) => SelDateTimeFormat -> m DateTimeFormat
|
||||||
|
getDateTimeFormat sel = do
|
||||||
|
mauth <- liftHandlerT maybeAuth
|
||||||
|
AppSettings{..} <- getsYesod appSettings
|
||||||
|
let
|
||||||
|
fmt
|
||||||
|
| Just (Entity _ User{..}) <- mauth
|
||||||
|
= case sel of
|
||||||
|
SelFormatDateTime -> userDateTimeFormat
|
||||||
|
SelFormatDate -> userDateFormat
|
||||||
|
SelFormatTime -> userTimeFormat
|
||||||
|
| otherwise
|
||||||
|
= case sel of
|
||||||
|
SelFormatDateTime -> appDefaultDateTimeFormat
|
||||||
|
SelFormatDate -> appDefaultDateFormat
|
||||||
|
SelFormatTime -> appDefaultTimeFormat
|
||||||
|
return fmt
|
||||||
|
|
||||||
|
validDateTimeFormats :: TimeLocale -> SelDateTimeFormat -> Set DateTimeFormat
|
||||||
|
-- ^ We use a whitelist instead of just letting the user specify their own format string since vulnerabilities in printf-like functions are not uncommon
|
||||||
|
validDateTimeFormats _ SelFormatDateTime = Set.fromList $
|
||||||
|
[ DateTimeFormat "%a %d %b %Y %R"
|
||||||
|
, DateTimeFormat "%a %b %d %Y %R"
|
||||||
|
, DateTimeFormat "%A, %d %B %Y %R"
|
||||||
|
, DateTimeFormat "%A, %B %d %Y %R"
|
||||||
|
, DateTimeFormat "%a %d %b %Y %T"
|
||||||
|
, DateTimeFormat "%a %b %d %Y %T"
|
||||||
|
, DateTimeFormat "%A, %d %B %Y %T"
|
||||||
|
, DateTimeFormat "%A, %B %d %Y %T"
|
||||||
|
, DateTimeFormat "%d.%m.%Y %R"
|
||||||
|
, DateTimeFormat "%d.%m.%Y %T"
|
||||||
|
, DateTimeFormat "%R %d.%m.%Y"
|
||||||
|
, DateTimeFormat "%T %d.%m.%Y"
|
||||||
|
, DateTimeFormat "%Y-%m-%d %R"
|
||||||
|
, DateTimeFormat "%Y-%m-%d %T"
|
||||||
|
, DateTimeFormat "%Y-%m-%dT%T"
|
||||||
|
]
|
||||||
|
validDateTimeFormats _ SelFormatDate = Set.fromList $
|
||||||
|
[ DateTimeFormat "%a %d %b %Y"
|
||||||
|
, DateTimeFormat "%a %b %d %Y"
|
||||||
|
, DateTimeFormat "%A, %d %B %Y"
|
||||||
|
, DateTimeFormat "%A, %B %d %Y"
|
||||||
|
, DateTimeFormat "%d.%m.%Y"
|
||||||
|
, DateTimeFormat "%Y-%m-%d"
|
||||||
|
]
|
||||||
|
validDateTimeFormats TimeLocale{..} SelFormatTime = Set.fromList . concat . catMaybes $
|
||||||
|
[ Just
|
||||||
|
[ DateTimeFormat "%R"
|
||||||
|
, DateTimeFormat "%T"
|
||||||
|
]
|
||||||
|
, do
|
||||||
|
guard $ uncurry (/=) amPm
|
||||||
|
Just
|
||||||
|
[ DateTimeFormat "%I:%M %p"
|
||||||
|
, DateTimeFormat "%I:%M %P"
|
||||||
|
, DateTimeFormat "%I:%M:%S %p"
|
||||||
|
, DateTimeFormat "%I:%M:%S %P"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
dateTimeFormatOptions :: (MonadHandler m, HandlerSite m ~ UniWorX) => SelDateTimeFormat -> m (OptionList DateTimeFormat)
|
||||||
|
dateTimeFormatOptions sel = do
|
||||||
|
now <- liftIO getCurrentTime
|
||||||
|
tl <- getTimeLocale
|
||||||
|
|
||||||
|
let
|
||||||
|
toOption fmt@DateTimeFormat{..} = do
|
||||||
|
dateTime <- formatTime' unDateTimeFormat now
|
||||||
|
return $ (dateTime, fmt)
|
||||||
|
|
||||||
|
optionsPairs <=< mapM toOption . Set.toList $ validDateTimeFormats tl sel
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import Handler.Utils.Form.Types
|
|||||||
import Handler.Utils.Templates
|
import Handler.Utils.Templates
|
||||||
|
|
||||||
import Handler.Utils.DateTime
|
import Handler.Utils.DateTime
|
||||||
|
import qualified Data.Time as Time
|
||||||
|
|
||||||
import Import
|
import Import
|
||||||
import qualified Data.Char as Char
|
import qualified Data.Char as Char
|
||||||
@ -404,15 +405,16 @@ dayTimeField fs mutc = do
|
|||||||
-}
|
-}
|
||||||
|
|
||||||
|
|
||||||
utcTimeField :: (Monad m, RenderMessage (HandlerSite m) FormMessage) => Field m UTCTime
|
utcTimeField :: (MonadHandler m, HandlerSite m ~ UniWorX) => Field m UTCTime
|
||||||
-- StackOverflow: dayToUTC <$> (areq (jqueryDayField def {...}) settings Nothing)
|
-- StackOverflow: dayToUTC <$> (areq (jqueryDayField def {...}) settings Nothing)
|
||||||
-- TODO: Verify whether this is UTC or local time from Browser
|
-- Browser returns LocalTime
|
||||||
utcTimeField = Field
|
utcTimeField = Field
|
||||||
{ fieldParse = parseHelper $ readTime
|
{ fieldParse = parseHelperGen $ readTime
|
||||||
, fieldView = \theId name attrs val isReq ->
|
, fieldView = \theId name attrs val isReq -> do
|
||||||
|
val' <- either id id <$> traverse (formatTime' fieldTimeFormat) val
|
||||||
[whamlet|
|
[whamlet|
|
||||||
$newline never
|
$newline never
|
||||||
<input id="#{theId}" name="#{name}" *{attrs} type="datetime-local" :isReq:required value="#{either id showTime val}">
|
<input id="#{theId}" name="#{name}" *{attrs} type="datetime-local" :isReq:required value="#{val'}">
|
||||||
|]
|
|]
|
||||||
, fieldEnctype = UrlEncoded
|
, fieldEnctype = UrlEncoded
|
||||||
}
|
}
|
||||||
@ -420,15 +422,15 @@ utcTimeField = Field
|
|||||||
fieldTimeFormat :: String
|
fieldTimeFormat :: String
|
||||||
--fieldTimeFormat = "%e.%m.%y %k:%M"
|
--fieldTimeFormat = "%e.%m.%y %k:%M"
|
||||||
fieldTimeFormat = "%Y-%m-%dT%H:%M"
|
fieldTimeFormat = "%Y-%m-%dT%H:%M"
|
||||||
|
|
||||||
readTime :: Text -> Either FormMessage UTCTime
|
-- `defaultTimeLocale` is okay here, since `fieldTimeFormat` does not contain any
|
||||||
|
readTime :: Text -> Either UniWorXMessage UTCTime
|
||||||
readTime t =
|
readTime t =
|
||||||
case parseTimeM True germanTimeLocale fieldTimeFormat (T.unpack t) of
|
case localTimeToUTC <$> parseTimeM True defaultTimeLocale fieldTimeFormat (T.unpack t) of
|
||||||
(Just time) -> Right time
|
(Just (LTUUnique time _)) -> Right time
|
||||||
Nothing -> Left $ MsgInvalidEntry $ "Datum/Zeit Format: tt.mm.yy hh:mm " ++ t
|
(Just (LTUNone time _)) -> Right time -- FIXME: Should this be an error, too?
|
||||||
|
(Just (LTUAmbiguous _ _ _ _)) -> Left MsgAmbiguousUTCTime
|
||||||
showTime :: UTCTime -> Text
|
Nothing -> Left MsgInvalidDateTimeFormat
|
||||||
showTime = fromString . (formatTime germanTimeLocale fieldTimeFormat)
|
|
||||||
|
|
||||||
|
|
||||||
fsm :: RenderMessage UniWorX msg => msg -> FieldSettings UniWorX -- DEPRECATED
|
fsm :: RenderMessage UniWorX msg => msg -> FieldSettings UniWorX -- DEPRECATED
|
||||||
|
|||||||
@ -117,7 +117,7 @@ formatRating cID Rating{ ratingValues = Rating'{..}, ..} = let
|
|||||||
doc = renderPretty 1 45 $ foldr (<$$>) mempty
|
doc = renderPretty 1 45 $ foldr (<$$>) mempty
|
||||||
[ "= Bitte nur Bewertung und Kommentare ändern ="
|
[ "= Bitte nur Bewertung und Kommentare ändern ="
|
||||||
, "============================================="
|
, "============================================="
|
||||||
, "========== UniWorx Bewertungsdatei =========="
|
, "========== Uni2work Bewertungsdatei =========="
|
||||||
, "======= diese Datei ist UTF8 encodiert ======"
|
, "======= diese Datei ist UTF8 encodiert ======"
|
||||||
, "Informationen zum Übungsblatt:"
|
, "Informationen zum Übungsblatt:"
|
||||||
, indent 2 $ foldr (<$$>) mempty
|
, indent 2 $ foldr (<$$>) mempty
|
||||||
|
|||||||
@ -3,20 +3,19 @@ module Import.NoFoundation
|
|||||||
( module Import
|
( module Import
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import ClassyPrelude.Yesod as Import
|
import ClassyPrelude.Yesod as Import hiding (formatTime)
|
||||||
import Model as Import
|
import Model as Import
|
||||||
import Settings as Import
|
import Settings as Import
|
||||||
import Settings.StaticFiles as Import
|
import Settings.StaticFiles as Import
|
||||||
import Yesod.Auth as Import
|
import Yesod.Auth as Import
|
||||||
import Yesod.Core.Types as Import (loggerSet)
|
import Yesod.Core.Types as Import (loggerSet)
|
||||||
import Yesod.Default.Config2 as Import
|
import Yesod.Default.Config2 as Import
|
||||||
import Utils as Import
|
import Utils as Import
|
||||||
|
|
||||||
|
|
||||||
import Data.Fixed as Import
|
import Data.Fixed as Import
|
||||||
|
|
||||||
import CryptoID as Import
|
import CryptoID as Import
|
||||||
import Data.UUID as Import (UUID)
|
import Data.UUID as Import (UUID)
|
||||||
|
|
||||||
|
import Text.Lucius as Import
|
||||||
import Text.Lucius as Import
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
{-# LANGUAGE TemplateHaskell #-}
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
{-# LANGUAGE NoImplicitPrelude #-}
|
{-# LANGUAGE NoImplicitPrelude #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE DeriveGeneric, DeriveDataTypeable #-}
|
{-# LANGUAGE DeriveGeneric, DeriveDataTypeable, GeneralizedNewtypeDeriving #-}
|
||||||
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
|
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE ViewPatterns #-}
|
{-# LANGUAGE ViewPatterns #-}
|
||||||
{-- # LANGUAGE ExistentialQuantification #-} -- for DA type
|
{-- # LANGUAGE ExistentialQuantification #-} -- for DA type
|
||||||
@ -285,12 +285,15 @@ instance Default Theme where
|
|||||||
derivePersistField "Theme"
|
derivePersistField "Theme"
|
||||||
|
|
||||||
|
|
||||||
newtype ZIPArchiveName obj = ZIPArchiveName obj
|
newtype ZIPArchiveName obj = ZIPArchiveName { unZIPArchiveName :: obj }
|
||||||
deriving (Show, Read, Eq)
|
deriving (Show, Read, Eq)
|
||||||
|
|
||||||
instance PathPiece obj => PathPiece (ZIPArchiveName obj) where
|
instance PathPiece obj => PathPiece (ZIPArchiveName obj) where
|
||||||
fromPathPiece (map CI.mk . unpack -> s)
|
fromPathPiece = fmap ZIPArchiveName . fromPathPiece <=< (stripSuffix `on` CI.foldCase) ".zip"
|
||||||
| Just s' <- stripSuffix (map CI.mk ".zip") s = fromPathPiece . pack $ map CI.original s'
|
toPathPiece = (<> ".zip") . toPathPiece . unZIPArchiveName
|
||||||
| otherwise = Nothing
|
|
||||||
|
|
||||||
toPathPiece (ZIPArchiveName obj) = toPathPiece obj <> ".zip"
|
newtype DateTimeFormat = DateTimeFormat { unDateTimeFormat :: String }
|
||||||
|
deriving (Eq, Ord, Read, Show, ToJSON, FromJSON, PersistField, PersistFieldSql)
|
||||||
|
|
||||||
|
data SelDateTimeFormat = SelFormatDateTime | SelFormatDate | SelFormatTime
|
||||||
|
deriving (Eq, Ord, Read, Show, Enum, Bounded)
|
||||||
|
|||||||
@ -24,6 +24,8 @@ import Yesod.Default.Util (WidgetFileSettings,
|
|||||||
widgetFileNoReload,
|
widgetFileNoReload,
|
||||||
widgetFileReload)
|
widgetFileReload)
|
||||||
|
|
||||||
|
import Model
|
||||||
|
|
||||||
-- | Runtime settings to configure this application. These settings can be
|
-- | Runtime settings to configure this application. These settings can be
|
||||||
-- loaded from various sources: defaults, environment variables, config files,
|
-- loaded from various sources: defaults, environment variables, config files,
|
||||||
-- theoretically even a database.
|
-- theoretically even a database.
|
||||||
@ -59,8 +61,11 @@ data AppSettings = AppSettings
|
|||||||
, appSkipCombining :: Bool
|
, appSkipCombining :: Bool
|
||||||
-- ^ Perform no stylesheet/script combining
|
-- ^ Perform no stylesheet/script combining
|
||||||
|
|
||||||
, appDefaultFavourites :: Int
|
, appDefaultTheme :: Theme
|
||||||
-- ^ Initial Value for remembered Favourites
|
, appDefaultMaxFavourites :: Int
|
||||||
|
, appDefaultDateTimeFormat :: DateTimeFormat
|
||||||
|
, appDefaultDateFormat :: DateTimeFormat
|
||||||
|
, appDefaultTimeFormat :: DateTimeFormat
|
||||||
|
|
||||||
-- Example app-specific configuration values.
|
-- Example app-specific configuration values.
|
||||||
, appCopyright :: Text
|
, appCopyright :: Text
|
||||||
@ -75,6 +80,7 @@ data AppSettings = AppSettings
|
|||||||
-- ^ If set authenticate against a local password file
|
-- ^ If set authenticate against a local password file
|
||||||
, appAllowDeprecated :: Bool
|
, appAllowDeprecated :: Bool
|
||||||
-- ^ Indicate if deprecated routes are accessible for everyone
|
-- ^ Indicate if deprecated routes are accessible for everyone
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instance FromJSON AppSettings where
|
instance FromJSON AppSettings where
|
||||||
@ -101,7 +107,11 @@ instance FromJSON AppSettings where
|
|||||||
appMutableStatic <- o .:? "mutable-static" .!= defaultDev
|
appMutableStatic <- o .:? "mutable-static" .!= defaultDev
|
||||||
appSkipCombining <- o .:? "skip-combining" .!= defaultDev
|
appSkipCombining <- o .:? "skip-combining" .!= defaultDev
|
||||||
|
|
||||||
appDefaultFavourites <- o .: "userDefaultFavourites"
|
appDefaultMaxFavourites <- o .: "default-favourites"
|
||||||
|
appDefaultTheme <- o .: "default-theme"
|
||||||
|
appDefaultDateTimeFormat <- o .: "default-date-time-format"
|
||||||
|
appDefaultDateFormat <- o .: "default-date-format"
|
||||||
|
appDefaultTimeFormat <- o .: "default-time-format"
|
||||||
|
|
||||||
appCopyright <- o .: "copyright"
|
appCopyright <- o .: "copyright"
|
||||||
appAnalytics <- o .:? "analytics"
|
appAnalytics <- o .:? "analytics"
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import qualified Data.Char as Char
|
|||||||
|
|
||||||
import Utils.DB as Utils
|
import Utils.DB as Utils
|
||||||
import Utils.Common as Utils
|
import Utils.Common as Utils
|
||||||
|
import Utils.DateTime as Utils
|
||||||
|
|
||||||
import Text.Blaze (Markup, ToMarkup)
|
import Text.Blaze (Markup, ToMarkup)
|
||||||
|
|
||||||
|
|||||||
57
src/Utils/DateTime.hs
Normal file
57
src/Utils/DateTime.hs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{-# LANGUAGE NoImplicitPrelude
|
||||||
|
, TemplateHaskell
|
||||||
|
, QuasiQuotes
|
||||||
|
, StandaloneDeriving
|
||||||
|
, DeriveLift
|
||||||
|
#-}
|
||||||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
|
module Utils.DateTime
|
||||||
|
( timeLocaleMap
|
||||||
|
, TimeLocale(..)
|
||||||
|
, module Data.Time.Zones
|
||||||
|
, module Data.Time.Zones.TH
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude.Yesod hiding (lift)
|
||||||
|
import System.Locale.Read
|
||||||
|
|
||||||
|
import Data.Time (TimeZone(..), TimeLocale(..))
|
||||||
|
import Data.Time.Zones (TZ)
|
||||||
|
import Data.Time.Zones.TH (includeSystemTZ)
|
||||||
|
|
||||||
|
import Language.Haskell.TH
|
||||||
|
import Language.Haskell.TH.Syntax (Lift(..))
|
||||||
|
import Instances.TH.Lift ()
|
||||||
|
|
||||||
|
deriving instance Lift TimeZone
|
||||||
|
deriving instance Lift TimeLocale
|
||||||
|
|
||||||
|
-- $(timeLocaleMap _) :: [Lang] -> TimeLocale
|
||||||
|
timeLocaleMap :: [(Lang, String)] -- ^ Languages and matching locales, first is taken as default
|
||||||
|
-> ExpQ
|
||||||
|
timeLocaleMap [] = fail "Need at least one (language, locale)-pair"
|
||||||
|
timeLocaleMap extra@((_, defLocale):_) = do
|
||||||
|
localeMap <- newName "localeMap"
|
||||||
|
|
||||||
|
let
|
||||||
|
localeMap' = funD localeMap $ map matchLang extra ++ [reduceLangList, 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]) []
|
||||||
|
|
||||||
|
localeExp :: String -> ExpQ
|
||||||
|
localeExp = lift <=< runIO . getLocale . Just
|
||||||
|
|
||||||
|
letE [localeMap'] (varE localeMap)
|
||||||
@ -3,10 +3,10 @@
|
|||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE FunctionalDependencies #-}
|
{-# LANGUAGE FunctionalDependencies #-}
|
||||||
module Utils.Lens where
|
module Utils.Lens ( module Utils.Lens ) where
|
||||||
|
|
||||||
import Import.NoFoundation
|
import Import.NoFoundation
|
||||||
import Control.Lens
|
import Control.Lens as Utils.Lens
|
||||||
|
|
||||||
makeClassy_ ''Entity
|
makeClassy_ ''Entity
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,10 @@ packages:
|
|||||||
git: https://github.com/pngwjpgh/encoding.git
|
git: https://github.com/pngwjpgh/encoding.git
|
||||||
commit: 67bb87ceff53f0178c988dd4e15eeb2daee92b84
|
commit: 67bb87ceff53f0178c988dd4e15eeb2daee92b84
|
||||||
extra-dep: true
|
extra-dep: true
|
||||||
|
- location:
|
||||||
|
git: https://github.com/pngwjpgh/system-locale.git
|
||||||
|
commit: d803ce3607ac6813ac1a065acb423220f57dab3c
|
||||||
|
extra-dep: true
|
||||||
|
|
||||||
extra-deps:
|
extra-deps:
|
||||||
- colonnade-1.2.0
|
- colonnade-1.2.0
|
||||||
|
|||||||
@ -22,13 +22,13 @@
|
|||||||
#{participants}
|
#{participants}
|
||||||
$maybe capacity <- courseCapacity course
|
$maybe capacity <- courseCapacity course
|
||||||
\ von #{capacity}
|
\ von #{capacity}
|
||||||
$maybe regFrom <- courseRegisterFrom course
|
$maybe regFrom <- mRegFrom
|
||||||
<dt .deflist__dt>Anmeldezeitraum
|
<dt .deflist__dt>Anmeldezeitraum
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
<div>
|
<div>
|
||||||
Ab #{formatTimeGerWD regFrom}
|
Ab #{regFrom}
|
||||||
$maybe regTo <- courseRegisterTo course
|
$maybe regTo <- mRegTo
|
||||||
\ bis #{formatTimeGerWD regTo}
|
\ bis #{regTo}
|
||||||
$if registrationOpen
|
$if registrationOpen
|
||||||
<dt .deflist__dt>
|
<dt .deflist__dt>
|
||||||
<dd .deflist__dd>
|
<dd .deflist__dd>
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@ -152,13 +153,16 @@ h4 {
|
|||||||
|
|
||||||
/* LAYOUT */
|
/* LAYOUT */
|
||||||
.main {
|
.main {
|
||||||
|
position: relative;
|
||||||
min-height: calc(100vh - var(--header-height));
|
min-height: calc(100vh - var(--header-height));
|
||||||
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
min-height: calc(100vh - var(--header-height-collapsed));
|
min-height: calc(100vh - var(--header-height-collapsed));
|
||||||
|
padding: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +170,6 @@ h4 {
|
|||||||
position: relative;
|
position: relative;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-left: 24%;
|
|
||||||
transition: padding-left .2s ease-out;
|
transition: padding-left .2s ease-out;
|
||||||
|
|
||||||
> .container {
|
> .container {
|
||||||
@ -182,42 +185,36 @@ h4 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.logged-in {
|
||||||
|
.main__content {
|
||||||
|
padding-left: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 425px) {
|
||||||
|
.logged-in {
|
||||||
|
.main__content {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
.logged-in {
|
||||||
|
.main__content {
|
||||||
|
padding-left: calc(24% + 30px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
|
|
||||||
.main__content {
|
.logged-in {
|
||||||
padding-left: 300px;
|
.main__content {
|
||||||
}
|
padding-left: 320px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
|
|
||||||
.main__content {
|
|
||||||
padding-left: 50px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 425px) {
|
|
||||||
|
|
||||||
.main__content {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.main__content-body {
|
|
||||||
padding: 30px 40px 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
|
|
||||||
.main__content-body {
|
|
||||||
padding: 30px 20px 60px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 425px) {
|
|
||||||
|
|
||||||
.main__content-body {
|
|
||||||
padding: 20px 10px 60px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,7 +416,7 @@ input[type="button"].btn-info:hover,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* LIST MODIFIERS */
|
/* LIST MODIFIERS */
|
||||||
.list--inline > li {
|
.list--inline li {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,30 +1,23 @@
|
|||||||
<div .masthead>
|
|
||||||
<div .container>
|
|
||||||
<div .row>
|
|
||||||
<h1 .header>
|
|
||||||
#{sheetName sheet}
|
|
||||||
<div .container>
|
<div .container>
|
||||||
<div .bs-docs-section>
|
$maybe descr <- sheetDescription sheet
|
||||||
<div .row>
|
<h2 #description>Hinweise
|
||||||
<div .col-lg-12>
|
<p> #{descr}
|
||||||
<div .page-header>
|
|
||||||
|
|
||||||
$maybe descr <- sheetDescription sheet
|
<h3>Bewertung
|
||||||
<h2 #description>Hinweise
|
<p>
|
||||||
<p> #{descr}
|
#{display $ sheetType sheet}
|
||||||
<h3>Bewertung
|
|
||||||
<p> #{display $ sheetType sheet}
|
|
||||||
$maybe marking <- sheetMarkingText sheet
|
|
||||||
<p> #{marking}
|
|
||||||
<br>
|
|
||||||
Freigeschaltet ab:
|
|
||||||
\ #{formatTimeGerWD $ sheetActiveFrom sheet}
|
|
||||||
\ Abgabe bis:
|
|
||||||
\ #{formatTimeGerWD $ sheetActiveTo sheet}
|
|
||||||
|
|
||||||
<div .row>
|
$maybe marking <- sheetMarkingText sheet
|
||||||
<div .col-lg-12>
|
<p>
|
||||||
<h2>Dateien
|
#{marking}
|
||||||
^{fileTable}
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Freigeschaltet ab
|
||||||
|
#{sheetFrom}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Abgabe bis
|
||||||
|
#{sheetTo}
|
||||||
|
|
||||||
|
<h2>Dateien
|
||||||
|
^{fileTable}
|
||||||
|
|||||||
@ -43,10 +43,7 @@
|
|||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
animation: slide-in-alert .2s ease-out forwards;
|
animation: slide-in-alert .2s ease-out forwards;
|
||||||
|
margin-bottom: 20px;
|
||||||
+ .alert:not(.alert--invisible) {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
||||||
@ -100,13 +97,14 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|
||||||
.alert__content {
|
.alert__content {
|
||||||
padding: 4px 7px;
|
padding: 4px 7px;
|
||||||
padding-left: 20px;
|
padding-left: 25px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,4 +188,6 @@
|
|||||||
.alert--invisible {
|
.alert--invisible {
|
||||||
max-height: 0;
|
max-height: 0;
|
||||||
transform: translateX(120%);
|
transform: translateX(120%);
|
||||||
|
margin-bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,6 +59,7 @@
|
|||||||
resetFileLabel();
|
resetFileLabel();
|
||||||
input.classList.add('file-input__input--hidden');
|
input.classList.add('file-input__input--hidden');
|
||||||
input.addEventListener('change', function() {
|
input.addEventListener('change', function() {
|
||||||
|
input.dispatchEvent(new Event('input'));
|
||||||
if (isMulti) {
|
if (isMulti) {
|
||||||
renderFileList(input.files);
|
renderFileList(input.files);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,18 @@
|
|||||||
$maybe cID <- mcid
|
$maybe cID <- mcid
|
||||||
<section style="padding-bottom:1em; margin-bottom:1em; border-bottom:1px solid black;">
|
<section>
|
||||||
<h2>
|
<h2>
|
||||||
<a href=@{CSubmissionR tid csh shn cID (SubArchiveR (ZIPArchiveName SubmissionCorrected))}>Archiv
|
<a href=@{CSubmissionR tid csh shn cID (SubArchiveR (ZIPArchiveName SubmissionCorrected))}>Archiv
|
||||||
$forall (name,time) <- lastEdits
|
(<a href=@{CSubmissionR tid csh shn cID (SubArchiveR (ZIPArchiveName SubmissionOriginal))}>Original</a>)
|
||||||
<div>last edited by #{name} at #{formatTimeGerDTlong time}
|
$if not (null lastEdits)
|
||||||
|
<h3>_{MsgLastEdits}
|
||||||
|
<ul>
|
||||||
|
$forall (name,time) <- lastEdits
|
||||||
|
<li>_{MsgEditedBy name time}
|
||||||
$maybe fileTable <- mFileTable
|
$maybe fileTable <- mFileTable
|
||||||
<h3>Enthaltene Dateien:
|
<h3>_{MsgSubmissionFiles}
|
||||||
^{fileTable}
|
^{fileTable}
|
||||||
|
|
||||||
<section>
|
$if maySubmit
|
||||||
<form .form-horizontal method=post action=@{actionUrl} enctype=#{formEnctype}>
|
<section>
|
||||||
^{formWidget}
|
<form .form-horizontal method=post action=@{actionUrl} enctype=#{formEnctype}>
|
||||||
|
^{formWidget}
|
||||||
|
|||||||
@ -9,12 +9,12 @@ $newline never
|
|||||||
$nothing
|
$nothing
|
||||||
<tbody>
|
<tbody>
|
||||||
$if null wRows && (dbsEmptyStyle == DBESHeading)
|
$if null wRows && (dbsEmptyStyle == DBESHeading)
|
||||||
<tr>
|
<tr .table__row>
|
||||||
<td colspan=#{show columnCount}>
|
<td .table__td colspan=#{show columnCount}>
|
||||||
_{dbsEmptyMessage}
|
_{dbsEmptyMessage}
|
||||||
$else
|
$else
|
||||||
$forall row <- wRows
|
$forall row <- wRows
|
||||||
<tr .table__row>
|
<tr .table__row>
|
||||||
$forall widget <- row
|
$forall widget <- row
|
||||||
$# cell/body.hamlet
|
$# cell/body.hamlet
|
||||||
^{widget}
|
^{widget}
|
||||||
|
|||||||
@ -3,27 +3,21 @@
|
|||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function DOMContentLoaded() {
|
document.addEventListener('DOMContentLoaded', function DOMContentLoaded() {
|
||||||
|
|
||||||
var ASC = 'asc';
|
|
||||||
var DESC = 'desc';
|
|
||||||
|
|
||||||
function setupAsync(wrapper) {
|
function setupAsync(wrapper) {
|
||||||
|
|
||||||
var table = wrapper.querySelector('#' + #{String $ dbtIdent});
|
var table = wrapper.querySelector('#' + #{String $ dbtIdent});
|
||||||
var ths = Array.from(table.querySelectorAll('th.sortable'));
|
var ths = Array.from(table.querySelectorAll('th.sortable'));
|
||||||
if (ths) {
|
|
||||||
// attach click handler to each sortable column if any
|
|
||||||
ths.forEach(function(th) {
|
|
||||||
th.addEventListener('click', clickHandler);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var pagination = wrapper.querySelector('#' + #{String $ dbtIdent} + '-pagination');
|
var pagination = wrapper.querySelector('#' + #{String $ dbtIdent} + '-pagination');
|
||||||
|
|
||||||
|
ths.forEach(function(th) {
|
||||||
|
th.addEventListener('click', clickHandler);
|
||||||
|
});
|
||||||
|
|
||||||
if (pagination) {
|
if (pagination) {
|
||||||
var paginationLinks = Array.from(pagination.querySelectorAll('.pagination-link'));
|
Array.from(pagination.querySelectorAll('.pagination-link'))
|
||||||
// attach click handler to pagination links if any
|
.forEach(function(p) {
|
||||||
paginationLinks.forEach(function(p) {
|
p.addEventListener('click', clickHandler);
|
||||||
p.addEventListener('click', clickHandler);
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clickHandler(event) {
|
function clickHandler(event) {
|
||||||
@ -31,12 +25,20 @@
|
|||||||
var url = new URL(window.location.origin + window.location.pathname + getClickDestination(this));
|
var url = new URL(window.location.origin + window.location.pathname + getClickDestination(this));
|
||||||
url.searchParams.set(#{String $ wIdent "table-only"}, 'yes');
|
url.searchParams.set(#{String $ wIdent "table-only"}, 'yes');
|
||||||
updateTableFrom(url);
|
updateTableFrom(url);
|
||||||
|
|
||||||
|
|
||||||
|
ths.forEach(function(th) {
|
||||||
|
// th.removeEventListener('click', clickHandler);
|
||||||
|
console.log('removed handler from', th);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getClickDestination(el) {
|
function getClickDestination(el) {
|
||||||
var link = el.querySelector('a');
|
console.log(el);
|
||||||
if (!link) { return false; }
|
if (!el.querySelector('a')) {
|
||||||
return link.getAttribute('href');
|
return false;
|
||||||
|
}
|
||||||
|
return el.querySelector('a').getAttribute('href');
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetches new sorted table from url with params and replaces contents of current table
|
// fetches new sorted table from url with params and replaces contents of current table
|
||||||
@ -47,14 +49,19 @@
|
|||||||
'Accept': 'text/html'
|
'Accept': 'text/html'
|
||||||
}
|
}
|
||||||
}).then(function(response) {
|
}).then(function(response) {
|
||||||
var contentType = response.headers.get("content-type");
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw ('Looks like there was a problem fetching ' + url.toString() + '. Status Code: ' + response.status);
|
throw ('Looks like there was a problem fetching ' + url.toString() + '. Status Code: ' + response.status);
|
||||||
}
|
}
|
||||||
return response.text();
|
return response.text();
|
||||||
}).then(function(data) {
|
}).then(function(data) {
|
||||||
|
// remove listeners
|
||||||
|
ths.forEach(function(th) {
|
||||||
|
th.removeEventListener('click', clickHandler);
|
||||||
|
});
|
||||||
|
|
||||||
// replace contents of table body
|
// replace contents of table body
|
||||||
wrapper.innerHTML = data;
|
wrapper.innerHTML = data;
|
||||||
|
|
||||||
// set up async functionality again
|
// set up async functionality again
|
||||||
setupAsync(wrapper);
|
setupAsync(wrapper);
|
||||||
table.querySelector('tbody').innerHTML = data;
|
table.querySelector('tbody').innerHTML = data;
|
||||||
|
|||||||
@ -10,6 +10,17 @@
|
|||||||
Uni2work ist noch nicht abgeschlossen.
|
Uni2work ist noch nicht abgeschlossen.
|
||||||
^{features}
|
^{features}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<h2>
|
||||||
|
Bekannte Bugs
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Umlaute in Benutzernamen werden durch externes LDAP-Plugin entfernt
|
||||||
|
<li>
|
||||||
|
Auswahlbox "alle markieren" fehlt noch
|
||||||
|
<li>
|
||||||
|
Tabellen über mehrere Seiten müssen vor Seitenwechsel manuell sortiert werden
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<h2>
|
<h2>
|
||||||
Versionsgeschichte
|
Versionsgeschichte
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
function init() {
|
function init() {
|
||||||
var favoritesBtn = document.querySelector('.navbar__list-item--favorite');
|
var favoritesBtn = document.querySelector('.navbar__list-item--favorite');
|
||||||
favoritesBtn.addEventListener('click', function(event) {
|
favoritesBtn.addEventListener('click', function(event) {
|
||||||
|
favoritesBtn.classList.toggle('navbar__list-item--active');
|
||||||
asideEl.classList.toggle('main__aside--expanded');
|
asideEl.classList.toggle('main__aside--expanded');
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}, true);
|
}, true);
|
||||||
@ -19,21 +20,6 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
|
||||||
var asidenavEl = document.querySelector('.main__aside');
|
var asidenavEl = document.querySelector('.main__aside');
|
||||||
var mainContentEl = document.querySelector('.main__content');
|
|
||||||
|
|
||||||
function adjustHeight() {
|
|
||||||
window.requestAnimationFrame(function() {
|
|
||||||
asidenavEl.style.height = mainContentEl.clientHeight + 'px';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// unbeknownst to the user (below the fold), this happes slightly delayed
|
|
||||||
// because of dynamic changes to the styles inside the main__content
|
|
||||||
setTimeout(function() {
|
|
||||||
adjustHeight();
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
window.addEventListener('resize', adjustHeight);
|
|
||||||
|
|
||||||
window.utils.aside(asidenavEl);
|
window.utils.aside(asidenavEl);
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,22 @@
|
|||||||
.main__aside {
|
.main__aside {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
display: none;
|
||||||
background-color: var(--color-dark);
|
background-color: var(--color-dark);
|
||||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
flex: 0 0 300px;
|
flex: 0 0 300px;
|
||||||
min-height: calc(100% - var(--header-height));
|
min-height: calc(100% - var(--header-height));
|
||||||
transition: all .2s ease-out;
|
transition: all .2s ease-out;
|
||||||
width: 24%;
|
width: 24%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logged-in {
|
||||||
|
.main__aside {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* maximum width of 300px for wide screens */
|
/* maximum width of 300px for wide screens */
|
||||||
@ -168,7 +178,6 @@
|
|||||||
color: var(--color-font);
|
color: var(--color-font);
|
||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: all .2s ease-out;
|
|
||||||
width: 0;
|
width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
|
|||||||
@ -1,26 +1,9 @@
|
|||||||
.breadcrumbs__container {
|
.breadcrumbs__container {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: var(--color-font);
|
color: var(--color-font);
|
||||||
margin-left: 40px;
|
|
||||||
margin-top: 25px;
|
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
|
|
||||||
.breadcrumbs__container {
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 425px) {
|
|
||||||
|
|
||||||
.breadcrumbs__container {
|
|
||||||
margin-left: 10px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumbs__link {
|
.breadcrumbs__link {
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
@ -56,7 +56,6 @@
|
|||||||
min-width: 70px;
|
min-width: 70px;
|
||||||
height: calc(100% - 4px);
|
height: calc(100% - 4px);
|
||||||
padding: 0 6px 4px;
|
padding: 0 6px 4px;
|
||||||
box-shadow: 0 0 0 1px inset var(--color-lmu-box-border);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
@ -70,7 +69,14 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100% - 4px);
|
height: calc(100% - 4px);
|
||||||
padding: 0 6px 4px;
|
padding: 0 6px 4px;
|
||||||
box-shadow: 0 0 0 1px inset var(--color-lmu-box-border);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
|
||||||
|
.navbar__logo::before,
|
||||||
|
.navbar__logo::after {
|
||||||
|
border: 1px solid var(--color-lmu-box-border);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +120,10 @@
|
|||||||
min-width: 90px;
|
min-width: 90px;
|
||||||
color: var(--color-lightwhite);
|
color: var(--color-lightwhite);
|
||||||
transition: height .2s cubic-bezier(0.03, 0.43, 0.58, 1);
|
transition: height .2s cubic-bezier(0.03, 0.43, 0.58, 1);
|
||||||
box-shadow: 0 0 0 1px inset var(--color-lmu-box-border);
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-lightwhite);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar__link-label {
|
.navbar__link-label {
|
||||||
@ -123,6 +132,13 @@
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
|
||||||
|
.navbar__link-wrapper {
|
||||||
|
border: 1px solid var(--color-lmu-box-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|
||||||
.navbar__link-wrapper {
|
.navbar__link-wrapper {
|
||||||
@ -217,20 +233,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* "Favorites" list item, only visible on small screens */
|
/* "Favorites" list item, only visible on small screens and logged in */
|
||||||
.navbar__list {
|
.navbar__list-item {
|
||||||
|
&.navbar__list-item--favorite {
|
||||||
.navbar__list-item--favorite {
|
|
||||||
display: none;
|
display: none;
|
||||||
background-color: var(--color-primary);
|
}
|
||||||
|
}
|
||||||
|
.navbar__list-item--favorite {
|
||||||
|
display: none;
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logged-in {
|
||||||
|
.navbar__list {
|
||||||
|
li.navbar__list-item--favorite,
|
||||||
|
.navbar__list-item--favorite {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 425px) {
|
@media (min-width: 426px) {
|
||||||
|
|
||||||
.navbar__list {
|
.logged-in {
|
||||||
.navbar__list-item--favorite {
|
.navbar__list {
|
||||||
display: inline-block;
|
.navbar__list-item--favorite {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,7 +270,15 @@
|
|||||||
|
|
||||||
.navbar__link-wrapper {
|
.navbar__link-wrapper {
|
||||||
color: var(--color-grey);
|
color: var(--color-grey);
|
||||||
box-shadow: 0 0 0 1px inset var(--color-grey);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
|
||||||
|
.navbar__list-item--secondary {
|
||||||
|
.navbar__link-wrapper {
|
||||||
|
border: 1px solid var(--color-grey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user