Merge branch 'master' of gitlab.cip.ifi.lmu.de:jost/UniWorX into feat/exercises

This commit is contained in:
Gregor Kleen 2018-07-18 08:51:30 +02:00
commit 584d711cbc
38 changed files with 522 additions and 268 deletions

View File

@ -1,3 +1,6 @@
* Version 10.07.2018
Bugfixes, wählbares Format für Datum
* Version 04.07.2018
Hinweis eingefügt, dass alle Daten des Systems spätestens im Dezember 2018

View File

@ -40,7 +40,11 @@ ldap:
password: "_env:LDAPPW:"
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"

19
db.hs
View File

@ -5,6 +5,7 @@
{-# LANGUAGE PackageImports #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
import "uniworx" Import hiding (Option(..))
import "uniworx" Application (db, getAppDevSettings)
@ -47,7 +48,7 @@ main = do
fillDb :: DB ()
fillDb = do
defaultFavourites <- getsYesod $ appDefaultFavourites . appSettings
AppSettings{..} <- getsYesod appSettings
now <- liftIO getCurrentTime
let
summer2017 = TermIdentifier 2017 Summer
@ -60,7 +61,10 @@ fillDb = do
, userEmail = "G.Kleen@campus.lmu.de"
, userDisplayName = "Gregor Kleen"
, userMaxFavourites = 6
, userTheme = AberdeenReds
, userTheme = Default
, userDateTimeFormat = appDefaultDateTimeFormat
, userDateFormat = appDefaultDateFormat
, userTimeFormat = appDefaultTimeFormat
}
fhamann <- insert User
{ userPlugin = "LDAP"
@ -68,8 +72,11 @@ fillDb = do
, userMatrikelnummer = Nothing
, userEmail = "felix.hamann@campus.lmu.de"
, userDisplayName = "Felix Hamann"
, userMaxFavourites = defaultFavourites
, userMaxFavourites = appDefaultMaxFavourites
, userTheme = Default
, userDateTimeFormat = appDefaultDateTimeFormat
, userDateFormat = appDefaultDateFormat
, userTimeFormat = appDefaultTimeFormat
}
jost <- insert User
{ userPlugin = "LDAP"
@ -79,6 +86,9 @@ fillDb = do
, userDisplayName = "Steffen Jost"
, userMaxFavourites = 14
, userTheme = MossGreen
, userDateTimeFormat = appDefaultDateTimeFormat
, userDateFormat = appDefaultDateFormat
, userTimeFormat = appDefaultTimeFormat
}
void . insert $ User
{ userPlugin = "LDAP"
@ -88,6 +98,9 @@ fillDb = do
, userDisplayName = "Max Musterstudent"
, userMaxFavourites = 7
, userTheme = AberdeenReds
, userDateTimeFormat = appDefaultDateTimeFormat
, userDateFormat = appDefaultDateFormat
, userTimeFormat = appDefaultTimeFormat
}
void . insert $ Term
{ termName = summer2017

View File

@ -76,6 +76,7 @@ CorrectionHead tid@TermId courseShortHand@Text sheetName@Text cid@CryptoFileName
SubmissionMember g@Int: Mitabgebende(r) ##{display g}
SubmissionArchive: Zip-Archiv der Abgabedatei(en)
SubmissionFile: Datei zur Abgabe
SubmissionFiles: Abgegebene Dateien
SubmissionAlreadyExistsFor user@Text: #{user} hat bereits eine Abgabe zu diesem bÜbungsblatt.
CorrectionsTitle: Zugewiesene Korrekturen
@ -195,4 +196,14 @@ NoUpcomingSheetDeadlines: Keine anstehenden Übungsblätter
AdminFor: Administrator
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
View File

@ -4,8 +4,11 @@ User json
matrikelnummer Text Maybe
email Text
displayName Text
maxFavourites Int default=12
theme Theme default='default'
maxFavourites Int default=12
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
UniqueEmail email
deriving Show
@ -67,7 +70,7 @@ Course
registerTo UTCTime Maybe
deregisterUntil UTCTime Maybe
registerSecret Text Maybe -- Falls ein Passwort erforderlich ist
materialFree Bool default=true
materialFree Bool
CourseTermShort term shorthand
CourseEdit
user UserId

View File

@ -83,6 +83,9 @@ dependencies:
- MonadRandom
- email-validate
- scientific
- tz
- system-locale
- th-lift-instances
# The library contains all of our application code. The executable
# defined below is just a thin wrapper.

4
routes
View File

@ -64,10 +64,10 @@
/subs SSubsR GET POST
/subs/new SubmissionNewR GET POST !timeANDregistered
/subs/own SubmissionOwnR GET !free -- just redirect
/sub/#CryptoFileNameSubmission SubmissionR !corrector:
/sub/#CryptoFileNameSubmission SubmissionR !correctorANDisRead:
/ SubShowR GET POST !ownerANDtime !ownerANDisRead
/archive/#{ZIPArchiveName SubmissionFileType} SubArchiveR GET !owner
/correction CorrectionR GET POST !ownerANDisRead
/correction CorrectionR GET POST !corrector !ownerANDisRead
!/#SubmissionFileType/*FilePath SubDownloadR GET !owner
/correctors SCorrR GET POST
!/#SheetFileType/*FilePath SFileR GET !timeANDregistered !timeANDmaterials !corrector

View File

@ -72,8 +72,8 @@ import System.FilePath
import Handler.Utils.Templates
import Handler.Utils.StudyFeatures
import Handler.Utils.DateTime
import Control.Lens
import Utils
import Utils.Lens
import Data.Aeson
@ -88,9 +88,6 @@ import Text.Shakespeare.Text (st)
instance DisplayAble TermId where
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
display = toPathPiece -- requires import of Data.CryptoID here
-- -- MOVE ABOVE
@ -184,6 +181,13 @@ instance RenderMessage UniWorX SheetFileType where
SheetMarking -> renderMessage' MsgSheetMarking
where renderMessage' = renderMessage foundation ls
getTimeLocale' :: [Lang] -> TimeLocale
getTimeLocale' = $(timeLocaleMap [("de", "de_DE.utf8")])
appTZ :: TZ
appTZ = $(includeSystemTZ "Europe/Berlin")
-- Access Control
data AccessPredicate
= APPure (Route UniWorX -> Bool -> Reader MsgRenderer AuthResult)
@ -600,7 +604,7 @@ instance YesodBreadcrumbs UniWorX where
breadcrumb UsersR = return ("Benutzer", Just HomeR)
breadcrumb AdminTestR = return ("Test" , Just HomeR)
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 ProfileDataR = return ("Data" , Just ProfileR)
@ -999,9 +1003,14 @@ instance YesodAuth UniWorX where
userEmail <- maybe (throwError $ ServerError "Could not retrieve user email") return userEmail'
userDisplayName <- maybe (throwError $ ServerError "Could not retrieve user name") return userDisplayName'
AppSettings{..} <- getsYesod appSettings
let
userMaxFavourites = 12 -- TODO: appDefaultFavourites appSettings
userTheme = Default -- TODO: appDefaultFavourites appSettings
userMaxFavourites = appDefaultMaxFavourites
userTheme = appDefaultTheme
userDateTimeFormat = appDefaultDateTimeFormat
userDateFormat = appDefaultDateFormat
userTimeFormat = appDefaultTimeFormat
newUser = User{..}
userUpdate = [ UserMatrikelnummer =. userMatrikelnummer
, UserDisplayName =. userDisplayName

View File

@ -62,8 +62,8 @@ getTermCourseListR tid = do
[ sortable (Just "shorthand") (textCell MsgCourse) $ anchorCell'
(\(Entity _ Course{..}, _) -> CourseR courseTerm courseShorthand CShowR)
(\(Entity _ Course{..}, _) -> toWidget courseShorthand)
, sortable (Just "register-from") (textCell MsgRegisterFrom) $ \(Entity _ Course{..}, _) -> textCell $ display courseRegisterFrom
, sortable (Just "register-to") (textCell MsgRegisterTo) $ \(Entity _ Course{..}, _) -> textCell $ display courseRegisterTo
, sortable (Just "register-from") (textCell MsgRegisterFrom) $ \(Entity _ Course{..}, _) -> cell $ traverse (formatTime SelFormatDateTime) courseRegisterFrom >>= maybe mempty toWidget
, 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
Nothing -> MsgCourseMembersCount num
Just max -> MsgCourseMembersCountLimited num max
@ -110,6 +110,8 @@ getCShowR tid csh = do
let course = entityVal courseEnt
(regWidget, regEnctype) <- generateFormPost $ identifyForm "registerBtn" $ registerForm registered $ courseRegisterSecret course
registrationOpen <- (==Authorized) <$> isAuthorized (CourseR tid csh CRegisterR) True
mRegFrom <- traverse (formatTime SelFormatDateTime) $ courseRegisterFrom course
mRegTo <- traverse (formatTime SelFormatDateTime) $ courseRegisterTo course
defaultLayout $ do
setTitle $ [shamlet| #{toPathPiece tid} - #{csh}|]
$(widgetFile "course")

View File

@ -16,7 +16,7 @@ import Handler.Utils
import qualified Data.Map as Map
import Data.Time
import Data.Time hiding (formatTime)
-- import qualified Data.Text as T
-- import Yesod.Form.Bootstrap3
@ -73,7 +73,7 @@ homeAnonymous = do
, sortable (Just "term") (textCell MsgTerm) $ \DBRow{ dbrOutput=(Entity {entityVal = course}) } ->
textCell $ display $ courseTerm 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
{ 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, _, _) } ->
cell [whamlet|<a href=@{CSheetR tid csh shn SShowR}>#{display shn}|]
, 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) }) ->
case mbsid of
Nothing -> mempty

View File

@ -22,6 +22,9 @@ import Database.Esqueleto ((^.))
data SettingsForm = SettingsForm
{ stgMaxFavourties :: Int
, stgTheme :: Theme
, stgDateTime :: DateTimeFormat
, stgDate :: DateTimeFormat
, stgTime :: DateTimeFormat
}
makeSettingForm :: Maybe SettingsForm -> Form SettingsForm
@ -32,6 +35,9 @@ makeSettingForm template = identForm FIDsettings $ \html -> do
(fslpI MsgFavoriten "Anzahl Favoriten") (stgMaxFavourties <$> template)
<*> areq (selectFieldList themeList)
(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
return (result, widget) -- no validation required here
@ -43,13 +49,19 @@ getProfileR = do
let settingsTemplate = Just $ SettingsForm
{ stgMaxFavourties = userMaxFavourites
, stgTheme = userTheme
, stgDateTime = userDateTimeFormat
, stgDate = userDateFormat
, stgTime = userTimeFormat
}
((res,formWidget), formEnctype) <- runFormPost $ makeSettingForm settingsTemplate
case res of
(FormSuccess SettingsForm{..}) -> do
runDB $ do
update uid [ UserMaxFavourites =. stgMaxFavourties
, UserTheme =. stgTheme
update uid [ UserMaxFavourites =. stgMaxFavourties
, UserTheme =. stgTheme
, UserDateTimeFormat =. stgDateTime
, UserDateFormat =. stgDate
, UserTimeFormat =. stgTime
]
when (stgMaxFavourties < userMaxFavourites) $ do
-- prune Favourites to user-defined size

View File

@ -165,8 +165,8 @@ getSheetListR tid csh = do
let tid = courseTerm course
let colBase = mconcat
[ headed "Blatt" $ \(sid,sheet,_) -> simpleLink (toWgt $ sheetName sheet) $ CSheetR tid csh (sheetName sheet) SShowR
, headed "Abgabe ab" $ toWgt . formatTimeGerWD . sheetActiveFrom . snd3
, headed "Abgabe bis" $ toWgt . formatTimeGerWD . sheetActiveTo . snd3
, headed "Abgabe ab" $ \(_,Sheet{..},_) -> formatTime SelFormatDateTime sheetActiveFrom >>= toWidget
, headed "Abgabe bis" $ \(_,Sheet{..},_) -> formatTime SelFormatDateTime sheetActiveTo >>= toWidget
, headed "Bewertung" $ toWgt . display . sheetType . snd3
]
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
SheetHint -> textCell $ display $ sheetHintFrom 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
{ dbtSQLQuery = fileData
@ -249,8 +249,9 @@ getSShowR tid csh shn = do
}
defaultLayout $ do
setTitle $ toHtml $ T.append "Übung " $ sheetName sheet
sheetFrom <- formatTime SelFormatDateTime $ sheetActiveFrom sheet
sheetTo <- formatTime SelFormatDateTime $ sheetActiveTo sheet
$(widgetFile "sheetShow")
[whamlet| Under Construction !!! |] -- TODO
getSFileR :: TermId -> Text -> Text -> SheetFileType -> FilePath -> Handler TypedContent
getSFileR tid csh shn typ title = do

View File

@ -160,7 +160,7 @@ submissionHelper tid csh shn (SubmissionMode mcid) = do
E.orderBy [E.desc $ submissionEdit E.^. SubmissionEditTime]
E.limit numberOfSubmissionEditDates
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)
let unpackZips = True -- undefined -- TODO
((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 ()
actionUrl <- Data.Maybe.fromJust <$> getCurrentRoute
maySubmit <- (== Authorized) <$> isAuthorized actionUrl True
-- Maybe construct a table to display uploaded archive files
let colonnadeFiles :: _ -> Colonnade Sortable _ (DBCell (WidgetT UniWorX IO) ())
colonnadeFiles cid = mconcat
@ -355,7 +357,8 @@ getSubArchiveR tid csh shn cID@CryptoID{..} (ZIPArchiveName sfType) = do
fileSource' = do
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

View File

@ -46,19 +46,25 @@ getTermShowR = do
anchorCell' (\(Entity tid _, _) -> TermCourseListR tid)
(\(Entity tid _, _) -> [whamlet|#{display tid}|])
, sortable (Just "lecture-start") (i18nCell MsgLectureStart) $ \(Entity _ Term{..},_) ->
stringCell $ formatTimeGerWD termLectureStart
cell $ formatTime SelFormatDate termLectureStart >>= toWidget
, sortable (Just "lecture-end") "Ende Vorlesungen" $ \(Entity _ Term{..},_) ->
stringCell $ formatTimeGerWD termLectureEnd
cell $ formatTime SelFormatDate termLectureEnd >>= toWidget
, sortable Nothing "Aktiv" $ \(Entity _ Term{..},_) ->
textCell $ (bool "" tickmark termActive :: Text)
, sortable Nothing "Kurse" $ \(_, E.Value numCourses) ->
cell [whamlet|_{MsgNumCourses numCourses}|]
, sortable (Just "start") "Semesteranfang" $ \(Entity _ Term{..},_) ->
stringCell $ formatTimeGerWD termStart
cell $ formatTime SelFormatDate termStart >>= toWidget
, sortable (Just "end") "Semesterende" $ \(Entity _ Term{..},_) ->
stringCell $ formatTimeGerWD termEnd
cell $ formatTime SelFormatDate termEnd >>= toWidget
, 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 =
-- [ sortable Nothing "Edit" $ \(Entity tid Term{..},_) -> cell $ do

View File

@ -34,6 +34,12 @@ getUsersR = do
, sortable (Just "display-name") (i18nCell MsgName) $ \DBRow{ dbrOutput = Entity uid User{..} } -> anchorCellM
(AdminUserR <$> encrypt uid)
(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
{ dbCellContents = do
schools <- E.select . E.from $ \(school `E.InnerJoin` userAdmin) -> do
@ -78,6 +84,12 @@ getUsersR = do
[ ( "display-name"
, SortColumn $ \user -> user E.^. UserDisplayName
)
, ( "matriculation"
, SortColumn $ \user -> user E.^. UserMatrikelnummer
)
-- , ( "last-name"
-- , SortColumn $ \user -> (last . impureNonNull . words) <$> (user E.^. UserDisplayName)
-- )
]
, dbtFilter = mempty
, dbtStyle = def

View File

@ -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
germanTimeLocale = TimeLocale
{ 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?
}
import Data.Time.Zones hiding (localTimeToUTCFull)
import qualified Data.Time.Zones as TZ
formatTimeGer :: FormatTime t => String -> t -> String
formatTimeGer = formatTime germanTimeLocale
import Data.Time hiding (formatTime, localTimeToUTC, utcToLocalTime)
import qualified Data.Time.Format as Time
formatTimeGerDTlong :: FormatTime t => t -> String
formatTimeGerDTlong = formatTimeGer "%A, %e. %B %Y, %k:%M:%S"
import Data.Set (Set)
import qualified Data.Set as Set
formatTimeGerWDT :: FormatTime t => t -> String
formatTimeGerWDT = formatTimeGer $ dateTimeFmt germanTimeLocale
utcToLocalTime :: UTCTime -> LocalTime
utcToLocalTime = TZ.utcToLocalTimeTZ appTZ
formatTimeGerDT :: FormatTime t => t -> String -- 0.00.00 0:00
formatTimeGerDT = formatTimeGer "%e.%m.%y %k:%M" -- leading spaces at start, otherwise 0 padding
localTimeToUTC :: LocalTime -> LocalToUTCResult
localTimeToUTC = TZ.localTimeToUTCFull appTZ
-- the following is used by DisplayAble's display:
formatTimeGerDT2 :: FormatTime t => t -> String -- Day 00.00.00 00:00
formatTimeGerDT2 = formatTimeGer "%a %d.%m.%y %H:%M" -- always padding with 0
class FormatTime t => HasLocalTime t where
toLocalTime :: t -> LocalTime
formatTimeGerWD :: FormatTime t => t -> String
formatTimeGerWD = formatTimeGer "%a %e.%m.%y"
instance HasLocalTime LocalTime where
toLocalTime = id
formatTimeGerD :: FormatTime t => t -> String
formatTimeGerD = formatTimeGer $ dateFmt germanTimeLocale
instance HasLocalTime Day where
toLocalTime d = LocalTime d midnight
formatTimeGerT :: FormatTime t => t -> String
formatTimeGerT = formatTimeGer $ timeFmt germanTimeLocale
instance HasLocalTime UTCTime where
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

View File

@ -17,6 +17,7 @@ import Handler.Utils.Form.Types
import Handler.Utils.Templates
import Handler.Utils.DateTime
import qualified Data.Time as Time
import Import
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)
-- TODO: Verify whether this is UTC or local time from Browser
-- Browser returns LocalTime
utcTimeField = Field
{ fieldParse = parseHelper $ readTime
, fieldView = \theId name attrs val isReq ->
{ fieldParse = parseHelperGen $ readTime
, fieldView = \theId name attrs val isReq -> do
val' <- either id id <$> traverse (formatTime' fieldTimeFormat) val
[whamlet|
$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
}
@ -420,15 +422,15 @@ utcTimeField = Field
fieldTimeFormat :: String
--fieldTimeFormat = "%e.%m.%y %k:%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 =
case parseTimeM True germanTimeLocale fieldTimeFormat (T.unpack t) of
(Just time) -> Right time
Nothing -> Left $ MsgInvalidEntry $ "Datum/Zeit Format: tt.mm.yy hh:mm " ++ t
showTime :: UTCTime -> Text
showTime = fromString . (formatTime germanTimeLocale fieldTimeFormat)
case localTimeToUTC <$> parseTimeM True defaultTimeLocale fieldTimeFormat (T.unpack t) of
(Just (LTUUnique time _)) -> Right time
(Just (LTUNone time _)) -> Right time -- FIXME: Should this be an error, too?
(Just (LTUAmbiguous _ _ _ _)) -> Left MsgAmbiguousUTCTime
Nothing -> Left MsgInvalidDateTimeFormat
fsm :: RenderMessage UniWorX msg => msg -> FieldSettings UniWorX -- DEPRECATED

View File

@ -117,7 +117,7 @@ formatRating cID Rating{ ratingValues = Rating'{..}, ..} = let
doc = renderPretty 1 45 $ foldr (<$$>) mempty
[ "= Bitte nur Bewertung und Kommentare ändern ="
, "============================================="
, "========== UniWorx Bewertungsdatei =========="
, "========== Uni2work Bewertungsdatei =========="
, "======= diese Datei ist UTF8 encodiert ======"
, "Informationen zum Übungsblatt:"
, indent 2 $ foldr (<$$>) mempty

View File

@ -3,20 +3,19 @@ module Import.NoFoundation
( module Import
) where
import ClassyPrelude.Yesod as Import
import Model as Import
import Settings as Import
import Settings.StaticFiles as Import
import Yesod.Auth as Import
import Yesod.Core.Types as Import (loggerSet)
import Yesod.Default.Config2 as Import
import Utils as Import
import ClassyPrelude.Yesod as Import hiding (formatTime)
import Model as Import
import Settings as Import
import Settings.StaticFiles as Import
import Yesod.Auth as Import
import Yesod.Core.Types as Import (loggerSet)
import Yesod.Default.Config2 as Import
import Utils as Import
import Data.Fixed as Import
import Data.Fixed as Import
import CryptoID as Import
import Data.UUID as Import (UUID)
import CryptoID as Import
import Data.UUID as Import (UUID)
import Text.Lucius as Import
import Text.Lucius as Import

View File

@ -3,7 +3,7 @@
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveGeneric, DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric, DeriveDataTypeable, GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-}
{-# LANGUAGE ViewPatterns #-}
{-- # LANGUAGE ExistentialQuantification #-} -- for DA type
@ -285,12 +285,15 @@ instance Default Theme where
derivePersistField "Theme"
newtype ZIPArchiveName obj = ZIPArchiveName obj
newtype ZIPArchiveName obj = ZIPArchiveName { unZIPArchiveName :: obj }
deriving (Show, Read, Eq)
instance PathPiece obj => PathPiece (ZIPArchiveName obj) where
fromPathPiece (map CI.mk . unpack -> s)
| Just s' <- stripSuffix (map CI.mk ".zip") s = fromPathPiece . pack $ map CI.original s'
| otherwise = Nothing
fromPathPiece = fmap ZIPArchiveName . fromPathPiece <=< (stripSuffix `on` CI.foldCase) ".zip"
toPathPiece = (<> ".zip") . toPathPiece . unZIPArchiveName
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)

View File

@ -24,6 +24,8 @@ import Yesod.Default.Util (WidgetFileSettings,
widgetFileNoReload,
widgetFileReload)
import Model
-- | Runtime settings to configure this application. These settings can be
-- loaded from various sources: defaults, environment variables, config files,
-- theoretically even a database.
@ -59,8 +61,11 @@ data AppSettings = AppSettings
, appSkipCombining :: Bool
-- ^ Perform no stylesheet/script combining
, appDefaultFavourites :: Int
-- ^ Initial Value for remembered Favourites
, appDefaultTheme :: Theme
, appDefaultMaxFavourites :: Int
, appDefaultDateTimeFormat :: DateTimeFormat
, appDefaultDateFormat :: DateTimeFormat
, appDefaultTimeFormat :: DateTimeFormat
-- Example app-specific configuration values.
, appCopyright :: Text
@ -75,6 +80,7 @@ data AppSettings = AppSettings
-- ^ If set authenticate against a local password file
, appAllowDeprecated :: Bool
-- ^ Indicate if deprecated routes are accessible for everyone
}
instance FromJSON AppSettings where
@ -101,7 +107,11 @@ instance FromJSON AppSettings where
appMutableStatic <- o .:? "mutable-static" .!= 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"
appAnalytics <- o .:? "analytics"

View File

@ -19,6 +19,7 @@ import qualified Data.Char as Char
import Utils.DB as Utils
import Utils.Common as Utils
import Utils.DateTime as Utils
import Text.Blaze (Markup, ToMarkup)

57
src/Utils/DateTime.hs Normal file
View 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)

View File

@ -3,10 +3,10 @@
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
module Utils.Lens where
module Utils.Lens ( module Utils.Lens ) where
import Import.NoFoundation
import Control.Lens
import Control.Lens as Utils.Lens
makeClassy_ ''Entity

View File

@ -25,6 +25,10 @@ packages:
git: https://github.com/pngwjpgh/encoding.git
commit: 67bb87ceff53f0178c988dd4e15eeb2daee92b84
extra-dep: true
- location:
git: https://github.com/pngwjpgh/system-locale.git
commit: d803ce3607ac6813ac1a065acb423220f57dab3c
extra-dep: true
extra-deps:
- colonnade-1.2.0

View File

@ -22,13 +22,13 @@
#{participants}
$maybe capacity <- courseCapacity course
\ von #{capacity}
$maybe regFrom <- courseRegisterFrom course
$maybe regFrom <- mRegFrom
<dt .deflist__dt>Anmeldezeitraum
<dd .deflist__dd>
<div>
Ab #{formatTimeGerWD regFrom}
$maybe regTo <- courseRegisterTo course
\ bis #{formatTimeGerWD regTo}
Ab #{regFrom}
$maybe regTo <- mRegTo
\ bis #{regTo}
$if registrationOpen
<dt .deflist__dt>
<dd .deflist__dd>

View File

@ -24,6 +24,7 @@
box-sizing: border-box;
padding: 0;
margin: 0;
-webkit-font-smoothing: antialiased;
}
body {
@ -152,13 +153,16 @@ h4 {
/* LAYOUT */
.main {
position: relative;
min-height: calc(100vh - var(--header-height));
padding: 20px;
}
@media (max-width: 768px) {
.main {
min-height: calc(100vh - var(--header-height-collapsed));
padding: 10px;
}
}
@ -166,7 +170,6 @@ h4 {
position: relative;
background-color: white;
overflow: hidden;
padding-left: 24%;
transition: padding-left .2s ease-out;
> .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) {
.main__content {
padding-left: 300px;
}
}
@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;
.logged-in {
.main__content {
padding-left: 320px;
}
}
}
@ -419,7 +416,7 @@ input[type="button"].btn-info:hover,
}
/* LIST MODIFIERS */
.list--inline > li {
.list--inline li {
display: inline-block;
}

View File

@ -1,30 +1,23 @@
<div .masthead>
<div .container>
<div .row>
<h1 .header>
#{sheetName sheet}
<div .container>
<div .bs-docs-section>
<div .row>
<div .col-lg-12>
<div .page-header>
$maybe descr <- sheetDescription sheet
<h2 #description>Hinweise
<p> #{descr}
$maybe descr <- sheetDescription sheet
<h2 #description>Hinweise
<p> #{descr}
<h3>Bewertung
<p> #{display $ sheetType sheet}
$maybe marking <- sheetMarkingText sheet
<p> #{marking}
<br>
Freigeschaltet ab:
\ #{formatTimeGerWD $ sheetActiveFrom sheet}
\ Abgabe bis:
\ #{formatTimeGerWD $ sheetActiveTo sheet}
<h3>Bewertung
<p>
#{display $ sheetType sheet}
<div .row>
<div .col-lg-12>
<h2>Dateien
^{fileTable}
<hr>
$maybe marking <- sheetMarkingText sheet
<p>
#{marking}
<p>
Freigeschaltet ab
#{sheetFrom}
<p>
Abgabe bis
#{sheetTo}
<h2>Dateien
^{fileTable}

View File

@ -43,10 +43,7 @@
padding-left: 20px;
margin-left: 20px;
animation: slide-in-alert .2s ease-out forwards;
+ .alert:not(.alert--invisible) {
margin-top: 20px;
}
margin-bottom: 20px;
&:hover {
@ -100,13 +97,14 @@
font-weight: 600;
justify-content: flex-end;
align-items: center;
text-align: left;
}
@media (max-width: 768px) {
.alert__content {
padding: 4px 7px;
padding-left: 20px;
padding-left: 25px;
}
}
@ -190,4 +188,6 @@
.alert--invisible {
max-height: 0;
transform: translateX(120%);
margin-bottom: 0;
overflow: hidden;
}

View File

@ -59,6 +59,7 @@
resetFileLabel();
input.classList.add('file-input__input--hidden');
input.addEventListener('change', function() {
input.dispatchEvent(new Event('input'));
if (isMulti) {
renderFileList(input.files);
}

View File

@ -1,13 +1,18 @@
$maybe cID <- mcid
<section style="padding-bottom:1em; margin-bottom:1em; border-bottom:1px solid black;">
<section>
<h2>
<a href=@{CSubmissionR tid csh shn cID (SubArchiveR (ZIPArchiveName SubmissionCorrected))}>Archiv
$forall (name,time) <- lastEdits
<div>last edited by #{name} at #{formatTimeGerDTlong time}
(<a href=@{CSubmissionR tid csh shn cID (SubArchiveR (ZIPArchiveName SubmissionOriginal))}>Original</a>)
$if not (null lastEdits)
<h3>_{MsgLastEdits}
<ul>
$forall (name,time) <- lastEdits
<li>_{MsgEditedBy name time}
$maybe fileTable <- mFileTable
<h3>Enthaltene Dateien:
<h3>_{MsgSubmissionFiles}
^{fileTable}
<section>
<form .form-horizontal method=post action=@{actionUrl} enctype=#{formEnctype}>
^{formWidget}
$if maySubmit
<section>
<form .form-horizontal method=post action=@{actionUrl} enctype=#{formEnctype}>
^{formWidget}

View File

@ -9,12 +9,12 @@ $newline never
$nothing
<tbody>
$if null wRows && (dbsEmptyStyle == DBESHeading)
<tr>
<td colspan=#{show columnCount}>
<tr .table__row>
<td .table__td colspan=#{show columnCount}>
_{dbsEmptyMessage}
$else
$forall row <- wRows
<tr .table__row>
$forall widget <- row
$# cell/body.hamlet
$# cell/body.hamlet
^{widget}

View File

@ -3,27 +3,21 @@
document.addEventListener('DOMContentLoaded', function DOMContentLoaded() {
var ASC = 'asc';
var DESC = 'desc';
function setupAsync(wrapper) {
var table = wrapper.querySelector('#' + #{String $ dbtIdent});
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');
ths.forEach(function(th) {
th.addEventListener('click', clickHandler);
});
if (pagination) {
var paginationLinks = Array.from(pagination.querySelectorAll('.pagination-link'));
// attach click handler to pagination links if any
paginationLinks.forEach(function(p) {
p.addEventListener('click', clickHandler);
});
Array.from(pagination.querySelectorAll('.pagination-link'))
.forEach(function(p) {
p.addEventListener('click', clickHandler);
});
}
function clickHandler(event) {
@ -31,12 +25,20 @@
var url = new URL(window.location.origin + window.location.pathname + getClickDestination(this));
url.searchParams.set(#{String $ wIdent "table-only"}, 'yes');
updateTableFrom(url);
ths.forEach(function(th) {
// th.removeEventListener('click', clickHandler);
console.log('removed handler from', th);
});
}
function getClickDestination(el) {
var link = el.querySelector('a');
if (!link) { return false; }
return link.getAttribute('href');
console.log(el);
if (!el.querySelector('a')) {
return false;
}
return el.querySelector('a').getAttribute('href');
}
// fetches new sorted table from url with params and replaces contents of current table
@ -47,14 +49,19 @@
'Accept': 'text/html'
}
}).then(function(response) {
var contentType = response.headers.get("content-type");
if (!response.ok) {
throw ('Looks like there was a problem fetching ' + url.toString() + '. Status Code: ' + response.status);
}
return response.text();
}).then(function(data) {
// remove listeners
ths.forEach(function(th) {
th.removeEventListener('click', clickHandler);
});
// replace contents of table body
wrapper.innerHTML = data;
// set up async functionality again
setupAsync(wrapper);
table.querySelector('tbody').innerHTML = data;

View File

@ -10,6 +10,17 @@
Uni2work ist noch nicht abgeschlossen.
^{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>
<h2>
Versionsgeschichte

View File

@ -8,6 +8,7 @@
function init() {
var favoritesBtn = document.querySelector('.navbar__list-item--favorite');
favoritesBtn.addEventListener('click', function(event) {
favoritesBtn.classList.toggle('navbar__list-item--active');
asideEl.classList.toggle('main__aside--expanded');
event.preventDefault();
}, true);
@ -19,21 +20,6 @@
document.addEventListener('DOMContentLoaded', function() {
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);

View File

@ -1,12 +1,22 @@
.main__aside {
position: absolute;
display: none;
background-color: var(--color-dark);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
z-index: 1;
top: 0;
left: 0;
flex: 0 0 300px;
min-height: calc(100% - var(--header-height));
transition: all .2s ease-out;
width: 24%;
height: 100%;
}
.logged-in {
.main__aside {
display: block;
}
}
/* maximum width of 300px for wide screens */
@ -168,7 +178,6 @@
color: var(--color-font);
transform: translateX(0);
opacity: 0;
transition: all .2s ease-out;
width: 0;
overflow: hidden;
z-index: -1;

View File

@ -1,26 +1,9 @@
.breadcrumbs__container {
position: relative;
color: var(--color-font);
margin-left: 40px;
margin-top: 25px;
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 {
&:hover {

View File

@ -56,7 +56,6 @@
min-width: 70px;
height: calc(100% - 4px);
padding: 0 6px 4px;
box-shadow: 0 0 0 1px inset var(--color-lmu-box-border);
}
&::after {
@ -70,7 +69,14 @@
width: 100%;
height: calc(100% - 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;
color: var(--color-lightwhite);
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 {
@ -123,6 +132,13 @@
text-transform: uppercase;
}
@media (min-width: 769px) {
.navbar__link-wrapper {
border: 1px solid var(--color-lmu-box-border);
}
}
@media (max-width: 768px) {
.navbar__link-wrapper {
@ -217,20 +233,33 @@
}
}
/* "Favorites" list item, only visible on small screens */
.navbar__list {
.navbar__list-item--favorite {
/* "Favorites" list item, only visible on small screens and logged in */
.navbar__list-item {
&.navbar__list-item--favorite {
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 {
.navbar__list-item--favorite {
display: inline-block;
.logged-in {
.navbar__list {
.navbar__list-item--favorite {
display: none !important;
}
}
}
}
@ -241,7 +270,15 @@
.navbar__link-wrapper {
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);
}
}
}