From a544c61be2d2b13690bc54286bafe4a25b6222c3 Mon Sep 17 00:00:00 2001 From: SJost Date: Tue, 10 Apr 2018 12:50:20 +0200 Subject: [PATCH 001/559] Submission upload (Sitzung mit Gregor) --- messages/de.msg | 4 ++- routes | 17 ++++++---- src/CryptoID.hs | 14 ++++++++ src/Foundation.hs | 14 ++++---- src/Handler/CryptoIDDispatch.hs | 12 +++++-- src/Handler/Sheet.hs | 30 +---------------- src/Handler/Submission.hs | 60 ++++++++++++++++++++++++++++++--- src/Handler/Utils.hs | 7 ++-- src/Handler/Utils/Form.hs | 2 +- src/Model/Types.hs | 3 ++ templates/submission.hamlet | 2 +- 11 files changed, 110 insertions(+), 55 deletions(-) diff --git a/messages/de.msg b/messages/de.msg index fb15e4fea..44b84282e 100644 --- a/messages/de.msg +++ b/messages/de.msg @@ -23,4 +23,6 @@ UnauthorizedSchoolLecturer: Sie sind nicht als Veranstalter für dieses Institut UnauthorizedLecturer: Sie sind nicht als Veranstalter für diese Veranstaltung eingetragen. UnauthorizedCorrector: Sie sind nicht als Korrektor für diese Veranstaltung eingetragen. UnauthorizedParticipant: Sie sind nicht als Teilnehmer für diese Veranstaltung registriert. -OnlyUploadOneFile: Bitte nur eine Datei hochladen. \ No newline at end of file +OnlyUploadOneFile: Bitte nur eine Datei hochladen. +SubmissionWrongSheet: Abgabenummer gehört nicht zum angegebenen Übungsblatt. +SubmissionTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: #{termToText tid}-#{courseShortHand} #{sheetName}: Abgabe diff --git a/routes b/routes index 075a60fd4..9552688ea 100644 --- a/routes +++ b/routes @@ -20,17 +20,20 @@ /edit CourseEditR GET POST !lecturer /ex SheetR !registered: - / SheetListR GET - /#Text/show SheetShowR GET !time - /#Text/#SheetFileType/#FilePath SheetFileR GET !time - /new SheetNewR GET POST !lecturer - /#Text/edit SheetEditR GET POST !lecturer - /#Text/delete SheetDelR GET POST !lecturer + / SheetListR GET + /#Text/show SheetShowR GET !time + /#Text/#SheetFileType/#FilePath SheetFileR GET !time + /new SheetNewR GET POST !lecturer + /#Text/edit SheetEditR GET POST !lecturer + /#Text/delete SheetDelR GET POST !lecturer + !/#Text/submission/#SubmissionMode SubmissionR GET POST !time + + -- TODO below /submission SubmissionListR GET POST -/submission/#CryptoUUIDSubmission SubmissionR GET POST +/submission/#CryptoUUIDSubmission SubmissionDemoR GET POST /submissions.zip SubmissionDownloadMultiArchiveR POST !/submission/archive/#FilePath SubmissionDownloadArchiveR GET !/submission/#CryptoUUIDSubmission/#FilePath SubmissionDownloadSingleR GET diff --git a/src/CryptoID.hs b/src/CryptoID.hs index ed2864eab..7c04d7b3f 100644 --- a/src/CryptoID.hs +++ b/src/CryptoID.hs @@ -1,4 +1,5 @@ {-# LANGUAGE DataKinds #-} +{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} @@ -37,3 +38,16 @@ decCryptoIDs [ ''SubmissionId , ''FileId ] {- TODO: Do we need/want CryptoUUIDs for Sheet numbers? -} + + + +newtype SubmissionMode = SubmissionMode (Maybe CryptoUUIDSubmission) + deriving (Show, Read, Eq) + +instance PathPiece SubmissionMode where + fromPathPiece "new" = Just $ SubmissionMode Nothing + fromPathPiece s = SubmissionMode . Just <$> fromPathPiece s + + toPathPiece (SubmissionMode Nothing) = "new" + toPathPiece (SubmissionMode (Just x)) = toPathPiece x + diff --git a/src/Foundation.hs b/src/Foundation.hs index 1ca4de6fa..b4acfa24d 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -244,7 +244,7 @@ isAuthorizedDB route@(routeAttrs -> attrs) writeable isAuthorizedDB UsersR _ = adminAccess Nothing -isAuthorizedDB (SubmissionR cID) _ = submissionAccess $ Right cID +isAuthorizedDB (SubmissionDemoR cID) _ = return Authorized -- submissionAccess $ Right cID isAuthorizedDB (SubmissionDownloadSingleR cID _) _ = submissionAccess $ Right cID isAuthorizedDB (SubmissionDownloadArchiveR (splitExtension -> (baseName, _))) _ = submissionAccess . Left . CryptoID $ CI.mk baseName isAuthorizedDB TermEditR _ = adminAccess Nothing @@ -254,10 +254,11 @@ isAuthorizedDB (CourseR t c CourseEditR) _ = courseLecturerAccess . entity isAuthorizedDB (CourseR t c (SheetR SheetListR)) False = return Authorized -- isAuthorizedDB (CourseR t c (SheetR SheetListR)) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) isAuthorizedDB (CourseR t c (SheetR (SheetShowR s))) _ = return Authorized -- TODO: nur für angemeldete Kursteilnehmer falls sichtbar, sonst nur Lectrurer oder Korrektor -isAuthorizedDB (CourseR t c (SheetR (SheetFileR s _ _))) _ = return Authorized -- TODO: nur für angemeldete Kursteilnehmer falls sichtbar, sonst nur Lectrurer oder Korrektor -isAuthorizedDB (CourseR t c (SheetR SheetNewR)) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) -isAuthorizedDB (CourseR t c (SheetR (SheetEditR s))) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) -isAuthorizedDB (CourseR t c (SheetR (SheetDelR s))) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) +isAuthorizedDB (CourseR t c (SheetR (SheetFileR s _ _))) _ = return Authorized -- TODO: nur für angemeldete Kursteilnehmer falls sichtbar, sonst nur Lectrurer oder Korrektor +isAuthorizedDB (CourseR t c (SheetR SheetNewR)) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) +isAuthorizedDB (CourseR t c (SheetR (SheetEditR s))) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) +isAuthorizedDB (CourseR t c (SheetR (SheetDelR s))) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) +isAuthorizedDB (CourseR t c (SheetR (SubmissionR s m))) _ = return Authorized -- TODO -- submissionAccess $ Right cID isAuthorizedDB (CourseEditIDR cID) _ = do courseId <- decrypt cID courseLecturerAccess courseId @@ -339,9 +340,10 @@ instance YesodBreadcrumbs UniWorX where breadcrumb (CourseR tid csh (SheetR (SheetShowR shn))) = return (shn, Just $ CourseR tid csh $ SheetR SheetListR) breadcrumb (CourseR tid csh (SheetR (SheetEditR shn))) = return ("Edit", Just $ CourseR tid csh $ SheetR $ SheetShowR shn) breadcrumb (CourseR tid csh (SheetR (SheetDelR shn))) = return ("DELETE", Just $ CourseR tid csh $ SheetR $ SheetShowR shn) + breadcrumb (CourseR tid csh (SheetR (SubmissionR shn _))) = return ("Abgabe", Just $ CourseR tid csh $ SheetR $ SheetShowR shn) breadcrumb SubmissionListR = return ("Abgaben", Just HomeR) - breadcrumb (SubmissionR _) = return ("Abgabe", Just SubmissionListR) + breadcrumb HomeR = return ("UniworkY", Nothing) breadcrumb (AuthR _) = return ("Login", Just HomeR) diff --git a/src/Handler/CryptoIDDispatch.hs b/src/Handler/CryptoIDDispatch.hs index 0eff808f2..c604d3e45 100644 --- a/src/Handler/CryptoIDDispatch.hs +++ b/src/Handler/CryptoIDDispatch.hs @@ -20,6 +20,8 @@ import Import hiding (Proxy) import Data.Proxy +import Handler.Utils + import Yesod.Core.Types (HandlerContents(..), ErrorResponse(..)) import qualified Control.Monad.Catch as E (Handler(..)) @@ -30,9 +32,13 @@ class CryptoRoute ciphertext plaintext where instance CryptoRoute UUID SubmissionId where cryptoIDRoute _ (CryptoID -> cID) = do - (_ :: SubmissionId) <- decrypt cID - - return $ SubmissionR cID + (smid :: SubmissionId) <- decrypt cID + (tid,csh,shn) <- runDB $ do + shid <- submissionSheetId <$> get404 smid + Sheet{..} <- get404 shid + Course{..} <- get404 sheetCourseId + return (courseTermId, courseShorthand, sheetName) + return $ CourseR tid csh $ SheetR $ SubmissionR shn $ SubmissionMode $ Just cID class Dispatch ciphertext (x :: [*]) where diff --git a/src/Handler/Sheet.hs b/src/Handler/Sheet.hs index a7f672cc7..4f3b8bf9a 100644 --- a/src/Handler/Sheet.hs +++ b/src/Handler/Sheet.hs @@ -118,35 +118,7 @@ makeSheetForm msId template = identForm FIDsheet $ \html -> do ] ] -fetchSheetAux :: ( BaseBackend backend ~ SqlBackend - , E.SqlSelect b a - , Typeable a, MonadHandler m, IsPersistBackend backend - , PersistQueryRead backend, PersistUniqueRead backend - ) - => (E.SqlExpr (Entity Sheet) -> b) - -> Key Term -> Text -> Text -> ReaderT backend m a -fetchSheetAux prj tid csh shn = - let cachId = encodeUtf8 $ tshow (tid,csh,shn) - in cachedBy cachId $ do - -- Mit Yesod: - -- cid <- getKeyBy404 $ CourseTermShort tid csh - -- getBy404 $ CourseSheet cid shn - -- Mit Esqueleto: - sheetList <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do - E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourseId - E.where_ $ course E.^. CourseTermId E.==. E.val tid - E.&&. course E.^. CourseShorthand E.==. E.val csh - E.&&. sheet E.^. SheetName E.==. E.val shn - return $ prj sheet - case sheetList of - [sheet] -> return sheet - _other -> notFound -fetchSheet :: TermId -> Text -> Text -> YesodDB UniWorX (Entity Sheet) -fetchSheet = fetchSheetAux id - -fetchSheetId :: TermId -> Text -> Text -> YesodDB UniWorX (Key Sheet) -fetchSheetId tid cid shn = E.unValue <$> fetchSheetAux (E.^. SheetId) tid cid shn -- List Sheets getSheetListCID :: CourseId -> Handler Html @@ -205,6 +177,7 @@ getSheetList courseEnt = do then [whamlet|Es wurden noch keine Übungsblätter angelegt.|] else encodeWidgetTable tableDefault colSheets sheets + -- Show single sheet getSheetShowR :: TermId -> Text -> Text -> Handler Html getSheetShowR tid csh shn = do @@ -228,7 +201,6 @@ getSheetShowR tid csh shn = do $(widgetFile "sheetShow") [whamlet| Under Construction !!! |] -- TODO - getSheetFileR :: TermId -> Text -> Text -> SheetFileType -> FilePath -> Handler TypedContent getSheetFileR tid csh shn typ title = do content <- runDB $ E.select $ E.from $ diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index 3e2160d28..a321b4ddd 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -24,6 +24,7 @@ import Control.Monad.Trans.Maybe import Control.Monad.State.Class import Control.Monad.Trans.State.Strict (StateT) +import qualified Data.Maybe import qualified Data.Text as Text import qualified Data.Text.Encoding as Text import qualified Data.CaseInsensitive as CI @@ -42,6 +43,55 @@ import Colonnade import Yesod.Colonnade import qualified Text.Blaze.Html5.Attributes as HA + + +makeSubmissionForm :: Bool -> Form (Source Handler File) +makeSubmissionForm unpackZips = identForm FIDsubmission $ \html -> do + flip (renderAForm FormStandard) html $ + areq (zipFileField unpackZips) "Zip Archiv zur Abgabe" Nothing + <* submitButton + +getSubmissionR, postSubmissionR :: TermId -> Text -> Text -> SubmissionMode -> Handler Html +getSubmissionR = postSubmissionR +postSubmissionR tid csh shn (SubmissionMode mcid) = do + uid <- requireAuthId + msmid <- traverse decrypt mcid + shid <- runDB $ do + shid <- fetchSheetId tid csh shn + case msmid of + Nothing -> return shid + (Just smid) -> do + shid' <- submissionSheetId <$> get404 smid + when (shid /= shid') $ invalidArgsI [MsgSubmissionWrongSheet] + return shid + let unpackZips = True -- undefined -- TODO + ((res,formWidget), formEnctype) <- runFormPost $ makeSubmissionForm unpackZips + case res of + (FormSuccess files) -> do + smid <- runDB $ runConduit $ + transPipe lift files .| Conduit.map Left .| sinkSubmission shid uid ((,False) <$> msmid) + cID <- encrypt smid + redirect $ CourseR tid csh $ SheetR $ SubmissionR shn $ SubmissionMode $ Just cID + (FormFailure msgs) -> forM_ msgs $ (addMessage "warning") . toHtml + _other -> return () + + let pageTitle = MsgSubmissionTitle (unTermKey tid) csh shn + let formTitle = pageTitle + let formText = Nothing :: Maybe UniWorXMessage + actionUrl <- Data.Maybe.fromJust <$> getCurrentRoute + defaultLayout $ do + setTitleI pageTitle + $(widgetFile "formPageI18n") + + + + + + + +------------------------- DEMO BELOW + + submissionTable :: MForm Handler (FormResult [SubmissionId], Widget) submissionTable = do subs <- lift . runDB $ E.select . E.from $ \(sub `E.InnerJoin` sheet `E.InnerJoin` course) -> do @@ -56,7 +106,7 @@ submissionTable = do let anchorCourse (_, _, (_, _, Entity _ Course{..})) = CourseR courseTermId courseShorthand CourseShowR courseText (_, _, (_, _, Entity _ Course{..})) = toWidget courseName - anchorSubmission (_, cUUID, _) = SubmissionR cUUID + anchorSubmission (_, cUUID, _) = SubmissionDemoR cUUID submissionText (cID, _, _) = toWidget . toPathPiece . CI.foldedCase $ ciphertext cID colonnade = mconcat [ headed "Abgabe-ID" $ anchorCell anchorSubmission submissionText @@ -211,9 +261,11 @@ getSubmissionDownloadArchiveR path = do info = ZipInfo { zipComment = Text.encodeUtf8 . tshow $ ciphertext (cUUID :: CryptoUUIDSubmission) } fileEntitySource' =$= produceZip info =$= Conduit.map toFlushBuilder -getSubmissionR, postSubmissionR :: CryptoUUIDSubmission -> Handler Html -getSubmissionR = postSubmissionR -postSubmissionR cID = do + + +getSubmissionDemoR, postSubmissionDemoR :: CryptoUUIDSubmission -> Handler Html +getSubmissionDemoR = postSubmissionDemoR +postSubmissionDemoR cID = do submissionId <- decrypt cID ((uploadResult, uploadWidget), uploadEnctype) <- runFormPost . renderAForm FormStandard $ (,) diff --git a/src/Handler/Utils.hs b/src/Handler/Utils.hs index 33fc5f0f4..72a833f48 100644 --- a/src/Handler/Utils.hs +++ b/src/Handler/Utils.hs @@ -16,7 +16,8 @@ import Handler.Utils.Form as Handler.Utils import Handler.Utils.Table as Handler.Utils import Handler.Utils.Table.Pagination as Handler.Utils -import Handler.Utils.Zip as Handler.Utils -import Handler.Utils.Rating as Handler.Utils +import Handler.Utils.Zip as Handler.Utils +import Handler.Utils.Rating as Handler.Utils import Handler.Utils.Submission as Handler.Utils -import Handler.Utils.Templates as Handler.Utils +import Handler.Utils.Sheet as Handler.Utils +import Handler.Utils.Templates as Handler.Utils diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index 1936769ee..cfd104d15 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -44,7 +44,7 @@ import qualified Data.Set as Set -- Unique Form Identifiers to avoid accidents -- ------------------------------------------------ -data FormIdentifier = FIDcourse | FIDsheet +data FormIdentifier = FIDcourse | FIDsheet | FIDsubmission deriving (Enum, Eq, Ord, Bounded, Read, Show) diff --git a/src/Model/Types.hs b/src/Model/Types.hs index 9aac70705..da0073707 100644 --- a/src/Model/Types.hs +++ b/src/Model/Types.hs @@ -158,3 +158,6 @@ time `withinTerm` term = timeYear `mod` 100 == termYear `mod` 100 data StudyFieldType = FieldPrimary | FieldSecondary deriving (Eq, Ord, Enum, Show, Read, Bounded) derivePersistField "StudyFieldType" + + + diff --git a/templates/submission.hamlet b/templates/submission.hamlet index 5c678476a..d8ea8ae89 100644 --- a/templates/submission.hamlet +++ b/templates/submission.hamlet @@ -33,7 +33,7 @@
Abgabe ersetzen -
+ ^{uploadWidget}
From 5c1789786db7e0aaea0ad3a3714338727cd8873b Mon Sep 17 00:00:00 2001 From: SJost Date: Tue, 10 Apr 2018 15:16:32 +0200 Subject: [PATCH 002/559] Util-Sheet vergessen --- src/Handler/Utils/Sheet.hs | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/Handler/Utils/Sheet.hs diff --git a/src/Handler/Utils/Sheet.hs b/src/Handler/Utils/Sheet.hs new file mode 100644 index 000000000..61c5736dc --- /dev/null +++ b/src/Handler/Utils/Sheet.hs @@ -0,0 +1,49 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} + +module Handler.Utils.Sheet where + +import Import + +import qualified Database.Esqueleto as E +import qualified Database.Esqueleto.Internal.Sql as E + + + + +fetchSheetAux :: ( BaseBackend backend ~ SqlBackend + , E.SqlSelect b a + , Typeable a, MonadHandler m, IsPersistBackend backend + , PersistQueryRead backend, PersistUniqueRead backend + ) + => (E.SqlExpr (Entity Sheet) -> b) + -> TermId -> Text -> Text -> ReaderT backend m a +fetchSheetAux prj tid csh shn = + let cachId = encodeUtf8 $ tshow (tid,csh,shn) + in cachedBy cachId $ do + -- Mit Yesod: + -- cid <- getKeyBy404 $ CourseTermShort tid csh + -- getBy404 $ CourseSheet cid shn + -- Mit Esqueleto: + sheetList <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do + E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourseId + E.where_ $ course E.^. CourseTermId E.==. E.val tid + E.&&. course E.^. CourseShorthand E.==. E.val csh + E.&&. sheet E.^. SheetName E.==. E.val shn + return $ prj sheet + case sheetList of + [sheet] -> return sheet + _other -> notFound + +fetchSheet :: TermId -> Text -> Text -> YesodDB UniWorX (Entity Sheet) +fetchSheet = fetchSheetAux id + +fetchSheetId :: TermId -> Text -> Text -> YesodDB UniWorX (Key Sheet) +fetchSheetId tid cid shn = E.unValue <$> fetchSheetAux (E.^. SheetId) tid cid shn From fcd6703752ca8829fe5e9b4d267206514f2db9ee Mon Sep 17 00:00:00 2001 From: SJost Date: Wed, 11 Apr 2018 13:12:49 +0200 Subject: [PATCH 003/559] Group Submissions mostly done, NOT COMPILING --- messages/de.msg | 13 ++++ models | 3 +- src/Handler/Sheet.hs | 2 +- src/Handler/Submission.hs | 118 +++++++++++++++++++++++++++++++------ src/Handler/Utils/Form.hs | 2 +- src/Handler/Utils/Sheet.hs | 3 + src/Model/Types.hs | 2 +- 7 files changed, 121 insertions(+), 22 deletions(-) diff --git a/messages/de.msg b/messages/de.msg index 44b84282e..76cdbc62d 100644 --- a/messages/de.msg +++ b/messages/de.msg @@ -5,11 +5,13 @@ Page n@Int64 num@Int64: Seite #{tshow n} von #{tshow num} TermEdited tid@TermIdentifier: Semester #{termToText tid} erfolgreich editiert. TermNewTitle: Semester editiere/anlegen. InvalidInput: Eingaben bitte korrigieren. + CourseNewOk tid@TermIdentifier courseShortHand@Text: Kurs #{termToText tid}-#{courseShortHand} wurde erfolgreich erstellt. CourseEditOk tid@TermIdentifier courseShortHand@Text: Kurs #{termToText tid}-#{courseShortHand} wurde erfolgreich geändert. CourseNewDupShort tid@TermIdentifier courseShortHand@Text: Kurs #{termToText tid}-#{courseShortHand} konnte nicht erstellt werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{courseShortHand} in diesem Semester. CourseEditDupShort tid@TermIdentifier courseShortHand@Text: Kurs #{termToText tid}-#{courseShortHand} konnte nicht geändert werden: Es gibt bereits einen anderen Kurs mit dem Kürzel #{courseShortHand} in diesem Semester. FFSheetName: Name + SheetNewOk tid@TermIdentifier courseShortHand@Text sheetName@Text: Neues Übungsblatt #{sheetName} wurde im Kurs #{termToText tid}-#{courseShortHand} erfolgreich erstellt. SheetTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: #{termToText tid}-#{courseShortHand} #{sheetName} SheetTitleNew tid@TermIdentifier courseShortHand@Text : #{termToText tid}-#{courseShortHand}: Neues Übungsblatt @@ -18,11 +20,22 @@ SheetNameDup tid@TermIdentifier courseShortHand@Text sheetName@Text: Es gi SheetDelTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: Übungsblatt #{sheetName} wirklich aus Kurs #{termToText tid}-#{courseShortHand} herauslöschen? SheetDelText submissionNo@Int: Dies kann nicht mehr rückgängig gemacht werden! Alle Einreichungen gehen ebenfalls verloren! Es gibt #{show submissionNo} Abgaben. SheetDelOk tid@TermIdentifier courseShortHand@Text sheetName@Text: #{termToText tid}-#{courseShortHand}: Übungsblatt #{sheetName} gelöscht. + UnauthorizedSchoolAdmin: Sie sind nicht als Administrator für dieses Institut eingetragen. UnauthorizedSchoolLecturer: Sie sind nicht als Veranstalter für dieses Institut eingetragen. UnauthorizedLecturer: Sie sind nicht als Veranstalter für diese Veranstaltung eingetragen. UnauthorizedCorrector: Sie sind nicht als Korrektor für diese Veranstaltung eingetragen. UnauthorizedParticipant: Sie sind nicht als Teilnehmer für diese Veranstaltung registriert. OnlyUploadOneFile: Bitte nur eine Datei hochladen. + SubmissionWrongSheet: Abgabenummer gehört nicht zum angegebenen Übungsblatt. +SubmissionAlreadyExists: Sie haben bereits eine Abgabe zu diesem Übungsblatt. SubmissionTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: #{termToText tid}-#{courseShortHand} #{sheetName}: Abgabe +SubmissionMember: Mitabgebende(r) +SubmissionArchive: Zip-Archiv der Abgabedatei(en) +SubmissionFile: Datei zur Abgabe +SubmissionAlreadyExistsFor user@Text: #{user} hat bereits eine Abgabe zu diesem Übungsblatt. + +EMailUnknown email@Text: E-Mail #{email} gehört zu keinem bekannten Benutzer. +NotAParticipant user@Text tid@TermIdentifier csh@Text: #{user} ist nicht im Kurs #{termToText tid}-#{csh} angemeldet. + diff --git a/models b/models index de3c17b88..3e0c09966 100644 --- a/models +++ b/models @@ -6,6 +6,7 @@ User displayName Text maxFavourites Int default=12 UniqueAuthentication plugin ident + UniqueEmail email UserAdmin user UserId school SchoolId @@ -147,7 +148,7 @@ SubmissionUser UniqueSubmissionUser userId submissionId SubmissionGroup courseId CourseId - name Text + name Text Maybe SubmissionGroupEdit user UserId time UTCTime diff --git a/src/Handler/Sheet.hs b/src/Handler/Sheet.hs index 4f3b8bf9a..b6a3b148f 100644 --- a/src/Handler/Sheet.hs +++ b/src/Handler/Sheet.hs @@ -332,7 +332,7 @@ getSheetDelR tid csh shn = do (FormSuccess BtnDelete) -> do runDB $ fetchSheetId tid csh shn >>= deleteCascade -- TODO: deleteCascade löscht aber nicht die hochgeladenen Dateien!!! - setMessageI $ MsgSheetDelOk tident csh shn + addMessageI "info" $ MsgSheetDelOk tident csh shn redirect $ CSheetR tid csh SheetListR _other -> do submissionno <- runDB $ do diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index a321b4ddd..142a35bf7 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -9,6 +9,7 @@ {-# LANGUAGE TupleSections #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE PatternGuards #-} module Handler.Submission where @@ -34,46 +35,123 @@ import qualified Database.Esqueleto as E import qualified Data.Conduit.List as Conduit import Data.Conduit.ResumableSink +import Data.Set (Set) import Data.Map (Map) import qualified Data.Map as Map import System.FilePath -import Colonnade +import Colonnade hiding (bool) import Yesod.Colonnade import qualified Text.Blaze.Html5.Attributes as HA -makeSubmissionForm :: Bool -> Form (Source Handler File) -makeSubmissionForm unpackZips = identForm FIDsubmission $ \html -> do - flip (renderAForm FormStandard) html $ - areq (zipFileField unpackZips) "Zip Archiv zur Abgabe" Nothing +makeSubmissionForm :: Bool -> SheetGroup -> Form (Source Handler File, [Text]) +makeSubmissionForm unpackZips grouping = identForm FIDsubmission $ \html -> do + flip (renderAForm FormStandard) html $ (,) + <$> areq (zipFileField unpackZips) (fsm $ bool MsgSubmissionFile MsgSubmissionArchive unpackZips) Nothing + <*> (catMaybes <$> replicateM groupNr (aopt textField (fsm MsgSubmissionMember) Nothing)) -- TODO: Convenience: preselect last buddies <* submitButton + where + groupNr + | Arbitrary{..} <- grouping = pred maxParticipants + | otherwise = 0 getSubmissionR, postSubmissionR :: TermId -> Text -> Text -> SubmissionMode -> Handler Html getSubmissionR = postSubmissionR postSubmissionR tid csh shn (SubmissionMode mcid) = do uid <- requireAuthId msmid <- traverse decrypt mcid - shid <- runDB $ do - shid <- fetchSheetId tid csh shn + (Entity shid Sheet{..}) <- runDB $ do + sheet@(Entity shid _) <- fetchSheet tid csh shn case msmid of - Nothing -> return shid + Nothing -> do + submissions <- E.select . E.from $ \(submission `E.InnerJoin` submissionUser) -> do + E.on (submission E.^. SubmissionId E.==. submissionUser E.^. SubmissionUserSubmissionId) + E.where_ $ submissionUser E.^. SubmissionUserUserId E.==. E.val uid + E.&&. submission E.^. SubmissionSheetId E.==. E.val shid + return $ submission E.^. SubmissionId + $logDebugS "Submission.DUPLICATENEW" (tshow submissions) + case submissions of + [] -> return shid + (E.Value smid:_) -> do + cID <- encrypt smid + addMessageI "info" $ MsgSubmissionAlreadyExists + redirect $ CourseR tid csh $ SheetR $ SubmissionR shn $ SubmissionMode $ Just cID + return sheet (Just smid) -> do shid' <- submissionSheetId <$> get404 smid when (shid /= shid') $ invalidArgsI [MsgSubmissionWrongSheet] - return shid + return sheet let unpackZips = True -- undefined -- TODO - ((res,formWidget), formEnctype) <- runFormPost $ makeSubmissionForm unpackZips - case res of - (FormSuccess files) -> do - smid <- runDB $ runConduit $ - transPipe lift files .| Conduit.map Left .| sinkSubmission shid uid ((,False) <$> msmid) - cID <- encrypt smid - redirect $ CourseR tid csh $ SheetR $ SubmissionR shn $ SubmissionMode $ Just cID - (FormFailure msgs) -> forM_ msgs $ (addMessage "warning") . toHtml - _other -> return () + ((res,formWidget), formEnctype) <- runFormPost $ makeSubmissionForm unpackZips sheetGrouping + runDB $ do + res' <- case res of + (FormMissing ) -> return $ FormMissing + (FormFailure failmsg) -> return $ FormFailure failmsgs + (FormSuccess (files,[])) -> return $ FormSuccess (files,[]) -- Type change + (FormSuccess (files, (map CI.mk -> gEMails@(_:_)))) -- Validate AdHoc Group Members + | (Arbitrary {..}) <- sheetGrouping + , length gEMails < maxParticipants -> do -- < since submitting user is already accounted for + let gemails = map CI.foldedCase gEMails + prep ps = Map.fromList $ map (, Nothing) gEMails ++ [(CI.mk m, Just (i,p,s))|(E.Value m, (E.Value i, E.Value p, E.Value s)) <- ps] + participants <- fmap prep . E.select . E.from $ \user -> do + E.where_ $ (E.lower_ $ user E.^. UserEmail) `E.in_` E.valList gemails + isParticipant <- E.sub_select . E.from $ \courseParticipant -> do + E.where_ $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUserId + E.&&. courseParticipant E.^. CourseParticipantCourseId E.==. E.val cid + return $ E.countRows E.>. E.val 0 + hasSubmitted <- E.sub_select . E.from $ \(submissionUser `E.InnerJoin` submission) -> do + E.on $ submissionUser E.^. SubmissionUserSubmissionId E.==. submission E.^. SubmissionId + E.where_ $ submissionUser E.^. SubmissionUserUserId E.==. user E.^. UserId + E.&&. submission E.^. SubmissionSheetId E.==. E.val shid + return $ E.countRows E.>. E.val 0 + return (user E.^. UserEmail, (user E.^. UserId, isParticipant, hasSubmitted)) + $logDebugS "SUBMISSION.AdHocGroupValidation" $ tshow participants + mr <- getMessageRender + + let failmsgs = flip Map.foldMapWithKey participants $ + \email -> \case + Nothing -> [mr $ MsgEMailUnknown $ CI.original email] + (Just (_,False,_)) -> [mr $ MsgNotAParticipant (CI.original email) (unTermKey tid) csh] + (Just (_,_, True)) -> [mr $ MsgSubmissionAlreadyExistsFor (CI.original email)] + _other -> mempty + if null failmsgs + then return $ FormSuccess (files, foldMap (\(Just (i,_,_)) -> [i]) participants) + else return $ FormFailure failmsgs + + | otherwise -> return $ FormFailure ["Mismatching number of group participants"] + + + case res' of + (FormSuccess (files,gemails)) -> do + now <- liftIO $ getCurrentTime + smid <- runDB $ do + -- AdHoc + + -- + smid <- runConduit $ transPipe lift files .| Conduit.map Left .| sinkSubmission shid uid ((,False) <$> msmid) + insertUnique $ SubmissionUser uid smid + insert $ SubmissionEdit uid now smid + -- Gruppen Abgaben für Feste Gruppen + groupUids <- fmap setFromList . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup `E.InnerJoin` submissionGroupUser') -> do + E.on $ submissionGroup E.^. SubmissionGroupId E.==. submissionGroupUser' E.^. SubmissionGroupUserSubmissionGroupId + E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroupId E.==. submissionGroup E.^. SubmissionGroupId + E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUserId E.==. E.val uid + E.&&. submissionGroup E.^. SubmissionGroupCourseId E.==. E.val sheetCourseId + return $ submissionGroupUser' E.^. SubmissionGroupUserUserId + forM_ (groupUids :: Set (E.Value UserId)) $ \(E.Value uid') -> void . insertUnique $ SubmissionUser uid' smid + -- Adhoc Gruppen + + -- TODO + --TODO: SubmissionUser anlegen!!!! + --TODO: Permissions für GruppenAbgabe + return smid + cID <- encrypt smid + redirect $ CourseR tid csh $ SheetR $ SubmissionR shn $ SubmissionMode $ Just cID + (FormFailure msgs) -> forM_ msgs $ (addMessage "warning") . toHtml + _other -> return () let pageTitle = MsgSubmissionTitle (unTermKey tid) csh shn let formTitle = pageTitle @@ -89,6 +167,10 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do + + + +----------------------------------------------------------------------------------------------- ------------------------- DEMO BELOW diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index cfd104d15..1c5c94f1e 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -355,7 +355,7 @@ utcTimeField = Field fsm :: RenderMessage UniWorX msg => msg -> FieldSettings UniWorX -fsm = bfs +fsm = bfs -- TODO: get rid of Bootstrap fsb :: Text -> FieldSettings site fsb = bfs -- Just to avoid annoying Ambiguous Type Errors diff --git a/src/Handler/Utils/Sheet.hs b/src/Handler/Utils/Sheet.hs index 61c5736dc..24db7ae1a 100644 --- a/src/Handler/Utils/Sheet.hs +++ b/src/Handler/Utils/Sheet.hs @@ -47,3 +47,6 @@ fetchSheet = fetchSheetAux id fetchSheetId :: TermId -> Text -> Text -> YesodDB UniWorX (Key Sheet) fetchSheetId tid cid shn = E.unValue <$> fetchSheetAux (E.^. SheetId) tid cid shn + +fetchSheetIdCourseId :: TermId -> Text -> Text -> YesodDB UniWorX (Key Sheet, Key Course) +fetchSheetIdCourseId tid cid shn = bimap E.unValue E.unValue <$> fetchSheetAux ((,) <$> (E.^. SheetId) <*> (E.^. SheetCourseId)) tid cid shn diff --git a/src/Model/Types.hs b/src/Model/Types.hs index da0073707..449d947d7 100644 --- a/src/Model/Types.hs +++ b/src/Model/Types.hs @@ -54,7 +54,7 @@ deriveJSON defaultOptions ''SheetType derivePersistFieldJSON "SheetType" data SheetGroup - = Arbitrary { maxParticipants :: Int } -- Distinguish Limited/Arbitrary + = Arbitrary { maxParticipants :: Int } | RegisteredGroups | NoGroups deriving (Show, Read, Eq) From 4c4cbd584cd39328ddcf5abe2a244ce6e35f222c Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Wed, 11 Apr 2018 15:09:20 +0200 Subject: [PATCH 004/559] Fix build of Submission.hs --- messages/de.msg | 2 +- src/Handler/Submission.hs | 79 +++++++++++++++++++++------------------ 2 files changed, 43 insertions(+), 38 deletions(-) diff --git a/messages/de.msg b/messages/de.msg index 76cdbc62d..9c8582fad 100644 --- a/messages/de.msg +++ b/messages/de.msg @@ -31,7 +31,7 @@ OnlyUploadOneFile: Bitte nur eine Datei hochladen. SubmissionWrongSheet: Abgabenummer gehört nicht zum angegebenen Übungsblatt. SubmissionAlreadyExists: Sie haben bereits eine Abgabe zu diesem Übungsblatt. SubmissionTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: #{termToText tid}-#{courseShortHand} #{sheetName}: Abgabe -SubmissionMember: Mitabgebende(r) +SubmissionMember g@Int: Mitabgebende(r) ###{tshow g} SubmissionArchive: Zip-Archiv der Abgabedatei(en) SubmissionFile: Datei zur Abgabe SubmissionAlreadyExistsFor user@Text: #{user} hat bereits eine Abgabe zu diesem Übungsblatt. diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index 142a35bf7..304101890 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -28,6 +28,8 @@ import Control.Monad.Trans.State.Strict (StateT) import qualified Data.Maybe import qualified Data.Text as Text import qualified Data.Text.Encoding as Text + +import Data.CaseInsensitive (CI) import qualified Data.CaseInsensitive as CI import qualified Database.Esqueleto as E @@ -36,6 +38,7 @@ import qualified Data.Conduit.List as Conduit import Data.Conduit.ResumableSink import Data.Set (Set) +import qualified Data.Set as Set import Data.Map (Map) import qualified Data.Map as Map @@ -51,7 +54,7 @@ makeSubmissionForm :: Bool -> SheetGroup -> Form (Source Handler File, [Text]) makeSubmissionForm unpackZips grouping = identForm FIDsubmission $ \html -> do flip (renderAForm FormStandard) html $ (,) <$> areq (zipFileField unpackZips) (fsm $ bool MsgSubmissionFile MsgSubmissionArchive unpackZips) Nothing - <*> (catMaybes <$> replicateM groupNr (aopt textField (fsm MsgSubmissionMember) Nothing)) -- TODO: Convenience: preselect last buddies + <*> (catMaybes <$> sequenceA [aopt textField (fsm $ MsgSubmissionMember g) Nothing | g <- [1..groupNr] ]) -- TODO: Convenience: preselect last buddies <* submitButton where groupNr @@ -86,72 +89,74 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do return sheet let unpackZips = True -- undefined -- TODO ((res,formWidget), formEnctype) <- runFormPost $ makeSubmissionForm unpackZips sheetGrouping - runDB $ do + mCID <- runDB $ do res' <- case res of (FormMissing ) -> return $ FormMissing - (FormFailure failmsg) -> return $ FormFailure failmsgs + (FormFailure failmsgs) -> return $ FormFailure failmsgs (FormSuccess (files,[])) -> return $ FormSuccess (files,[]) -- Type change (FormSuccess (files, (map CI.mk -> gEMails@(_:_)))) -- Validate AdHoc Group Members | (Arbitrary {..}) <- sheetGrouping , length gEMails < maxParticipants -> do -- < since submitting user is already accounted for let gemails = map CI.foldedCase gEMails + prep :: [(E.Value Text, (E.Value UserId, E.Value Bool, E.Value Bool))] -> Map (CI Text) (Maybe (UserId, Bool, Bool)) prep ps = Map.fromList $ map (, Nothing) gEMails ++ [(CI.mk m, Just (i,p,s))|(E.Value m, (E.Value i, E.Value p, E.Value s)) <- ps] participants <- fmap prep . E.select . E.from $ \user -> do E.where_ $ (E.lower_ $ user E.^. UserEmail) `E.in_` E.valList gemails - isParticipant <- E.sub_select . E.from $ \courseParticipant -> do - E.where_ $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUserId - E.&&. courseParticipant E.^. CourseParticipantCourseId E.==. E.val cid - return $ E.countRows E.>. E.val 0 - hasSubmitted <- E.sub_select . E.from $ \(submissionUser `E.InnerJoin` submission) -> do - E.on $ submissionUser E.^. SubmissionUserSubmissionId E.==. submission E.^. SubmissionId - E.where_ $ submissionUser E.^. SubmissionUserUserId E.==. user E.^. UserId - E.&&. submission E.^. SubmissionSheetId E.==. E.val shid - return $ E.countRows E.>. E.val 0 + let + isParticipant = E.sub_select . E.from $ \courseParticipant -> do + E.where_ $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUserId + E.&&. courseParticipant E.^. CourseParticipantCourseId E.==. E.val sheetCourseId + return $ E.countRows E.>. E.val (0 :: Int64) + hasSubmitted = E.sub_select . E.from $ \(submissionUser `E.InnerJoin` submission) -> do + E.on $ submissionUser E.^. SubmissionUserSubmissionId E.==. submission E.^. SubmissionId + E.where_ $ submissionUser E.^. SubmissionUserUserId E.==. user E.^. UserId + E.&&. submission E.^. SubmissionSheetId E.==. E.val shid + return $ E.countRows E.>. E.val (0 :: Int64) return (user E.^. UserEmail, (user E.^. UserId, isParticipant, hasSubmitted)) $logDebugS "SUBMISSION.AdHocGroupValidation" $ tshow participants mr <- getMessageRender - let failmsgs = flip Map.foldMapWithKey participants $ - \email -> \case - Nothing -> [mr $ MsgEMailUnknown $ CI.original email] - (Just (_,False,_)) -> [mr $ MsgNotAParticipant (CI.original email) (unTermKey tid) csh] - (Just (_,_, True)) -> [mr $ MsgSubmissionAlreadyExistsFor (CI.original email)] - _other -> mempty - if null failmsgs - then return $ FormSuccess (files, foldMap (\(Just (i,_,_)) -> [i]) participants) - else return $ FormFailure failmsgs + let failmsgs = flip Map.foldMapWithKey participants $ \email -> \case + Nothing -> [mr $ MsgEMailUnknown $ CI.original email] + (Just (_,False,_)) -> [mr $ MsgNotAParticipant (CI.original email) (unTermKey tid) csh] + (Just (_,_, True)) -> [mr $ MsgSubmissionAlreadyExistsFor (CI.original email)] + _other -> mempty + return $ if null failmsgs + then FormSuccess (files, foldMap (\(Just (i,_,_)) -> [i]) participants) + else FormFailure failmsgs | otherwise -> return $ FormFailure ["Mismatching number of group participants"] case res' of - (FormSuccess (files,gemails)) -> do + (FormSuccess (files,(setFromList -> adhocIds))) -> do now <- liftIO $ getCurrentTime - smid <- runDB $ do - -- AdHoc - - -- + smid <- do smid <- runConduit $ transPipe lift files .| Conduit.map Left .| sinkSubmission shid uid ((,False) <$> msmid) + insertUnique $ SubmissionUser uid smid - insert $ SubmissionEdit uid now smid - -- Gruppen Abgaben für Feste Gruppen - groupUids <- fmap setFromList . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup `E.InnerJoin` submissionGroupUser') -> do + -- insert $ SubmissionEdit uid now smid -- sinkSubmission already does this + + -- Determine members of pre-registered group + groupUids <- fmap (setFromList . map E.unValue) . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup `E.InnerJoin` submissionGroupUser') -> do E.on $ submissionGroup E.^. SubmissionGroupId E.==. submissionGroupUser' E.^. SubmissionGroupUserSubmissionGroupId E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroupId E.==. submissionGroup E.^. SubmissionGroupId E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUserId E.==. E.val uid E.&&. submissionGroup E.^. SubmissionGroupCourseId E.==. E.val sheetCourseId return $ submissionGroupUser' E.^. SubmissionGroupUserUserId - forM_ (groupUids :: Set (E.Value UserId)) $ \(E.Value uid') -> void . insertUnique $ SubmissionUser uid' smid - -- Adhoc Gruppen - -- TODO - --TODO: SubmissionUser anlegen!!!! - --TODO: Permissions für GruppenAbgabe + -- SubmissionUser for all group members (pre-registered & ad-hoc) + forM_ (groupUids `Set.union` adhocIds) $ \uid' -> void . insertUnique $ SubmissionUser uid' smid + return smid cID <- encrypt smid - redirect $ CourseR tid csh $ SheetR $ SubmissionR shn $ SubmissionMode $ Just cID - (FormFailure msgs) -> forM_ msgs $ (addMessage "warning") . toHtml - _other -> return () + return $ Just cID + (FormFailure msgs) -> Nothing <$ forM_ msgs (addMessage "warning" . toHtml) + _other -> return Nothing + + case mCID of + Just cID -> redirect $ CourseR tid csh $ SheetR $ SubmissionR shn $ SubmissionMode $ Just cID + Nothing -> return () let pageTitle = MsgSubmissionTitle (unTermKey tid) csh shn let formTitle = pageTitle From 18f33290bb131d45473901e4f95b66ffdf5019a8 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Wed, 11 Apr 2018 15:17:44 +0200 Subject: [PATCH 005/559] Additional sheets for testing --- fill-db.hs | 6 +++++- messages/de.msg | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/fill-db.hs b/fill-db.hs index 4cc894464..205d4afd5 100755 --- a/fill-db.hs +++ b/fill-db.hs @@ -105,7 +105,11 @@ main = db $ do void . insert $ Lecturer jost ffp void . insert $ Lecturer gkleen ffp insert_ $ Corrector gkleen ffp (ByProportion 1) - sheetkey <- insert $ Sheet ffp "Blatt 1" Nothing NotGraded NoGroups Nothing Nothing now now Nothing Nothing + sheetkey <- insert $ Sheet ffp "AdHoc-Gruppen" Nothing NotGraded (Arbitrary 3) Nothing Nothing now now Nothing Nothing + insert_ $ SheetEdit gkleen now sheetkey + sheetkey <- insert $ Sheet ffp "Feste Gruppen" Nothing NotGraded RegisteredGroups Nothing Nothing now now Nothing Nothing + insert_ $ SheetEdit gkleen now sheetkey + sheetkey <- insert $ Sheet ffp "Keine Gruppen" Nothing NotGraded NoGroups Nothing Nothing now now Nothing Nothing insert_ $ SheetEdit gkleen now sheetkey -- EIP eip <- insert Course diff --git a/messages/de.msg b/messages/de.msg index 9c8582fad..70a11676a 100644 --- a/messages/de.msg +++ b/messages/de.msg @@ -31,7 +31,7 @@ OnlyUploadOneFile: Bitte nur eine Datei hochladen. SubmissionWrongSheet: Abgabenummer gehört nicht zum angegebenen Übungsblatt. SubmissionAlreadyExists: Sie haben bereits eine Abgabe zu diesem Übungsblatt. SubmissionTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: #{termToText tid}-#{courseShortHand} #{sheetName}: Abgabe -SubmissionMember g@Int: Mitabgebende(r) ###{tshow g} +SubmissionMember g@Int: Mitabgebende(r) ##{tshow g} SubmissionArchive: Zip-Archiv der Abgabedatei(en) SubmissionFile: Datei zur Abgabe SubmissionAlreadyExistsFor user@Text: #{user} hat bereits eine Abgabe zu diesem Übungsblatt. From a0d5589777bc646621542d7b373c18bc84821ece Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Fri, 13 Apr 2018 14:37:23 +0200 Subject: [PATCH 006/559] added static flatpickr js and css --- src/Foundation.hs | 2 ++ static/css/flatpickr.css | 13 +++++++++++++ static/js/flatpickr.js | 2 ++ 3 files changed, 17 insertions(+) create mode 100644 static/css/flatpickr.css create mode 100644 static/js/flatpickr.js diff --git a/src/Foundation.hs b/src/Foundation.hs index 223e9df81..cdac53232 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -452,8 +452,10 @@ defaultMenuLayout menu widget = do pc <- widgetToPageContent $ do addStylesheetRemote "https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,800,900" addScript $ StaticR js_featureChecker_js + addScript $ StaticR js_flatpickr_js addStylesheet $ StaticR css_fonts_css addStylesheet $ StaticR css_icons_css + addStylesheet $ StaticR css_flatpickr_css $(widgetFile "default-layout") $(widgetFile "standalone/modal") $(widgetFile "standalone/showHide") diff --git a/static/css/flatpickr.css b/static/css/flatpickr.css new file mode 100644 index 000000000..9d24b0240 --- /dev/null +++ b/static/css/flatpickr.css @@ -0,0 +1,13 @@ +.flatpickr-calendar{background:transparent;opacity:0;display:none;text-align:center;visibility:hidden;padding:0;-webkit-animation:none;animation:none;direction:ltr;border:0;font-size:14px;line-height:24px;border-radius:5px;position:absolute;width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-touch-action:manipulation;touch-action:manipulation;background:#fff;-webkit-box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);}.flatpickr-calendar.open,.flatpickr-calendar.inline{opacity:1;max-height:640px;visibility:visible}.flatpickr-calendar.open{display:inline-block;z-index:99999}.flatpickr-calendar.animate.open{-webkit-animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1);animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1)}.flatpickr-calendar.inline{display:block;position:relative;top:2px}.flatpickr-calendar.static{position:absolute;top:calc(100% + 2px);}.flatpickr-calendar.static.open{z-index:999;display:block}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7){-webkit-box-shadow:none !important;box-shadow:none !important}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1){-webkit-box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-calendar .hasWeeks .dayContainer,.flatpickr-calendar .hasTime .dayContainer{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.flatpickr-calendar .hasWeeks .dayContainer{border-left:0}.flatpickr-calendar.showTimeInput.hasTime .flatpickr-time{height:40px;border-top:1px solid #e6e6e6}.flatpickr-calendar.noCalendar.hasTime .flatpickr-time{height:auto}.flatpickr-calendar:before,.flatpickr-calendar:after{position:absolute;display:block;pointer-events:none;border:solid transparent;content:'';height:0;width:0;left:22px}.flatpickr-calendar.rightMost:before,.flatpickr-calendar.rightMost:after{left:auto;right:22px}.flatpickr-calendar:before{border-width:5px;margin:0 -5px}.flatpickr-calendar:after{border-width:4px;margin:0 -4px}.flatpickr-calendar.arrowTop:before,.flatpickr-calendar.arrowTop:after{bottom:100%}.flatpickr-calendar.arrowTop:before{border-bottom-color:#e6e6e6}.flatpickr-calendar.arrowTop:after{border-bottom-color:#fff}.flatpickr-calendar.arrowBottom:before,.flatpickr-calendar.arrowBottom:after{top:100%}.flatpickr-calendar.arrowBottom:before{border-top-color:#e6e6e6}.flatpickr-calendar.arrowBottom:after{border-top-color:#fff}.flatpickr-calendar:focus{outline:0}.flatpickr-wrapper{position:relative;display:inline-block}.flatpickr-months{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}.flatpickr-months .flatpickr-month{background:transparent;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9);height:28px;line-height:1;text-align:center;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:hidden;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.flatpickr-months .flatpickr-prev-month,.flatpickr-months .flatpickr-next-month{text-decoration:none;cursor:pointer;position:absolute;top:0;line-height:16px;height:28px;padding:10px;z-index:3;}.flatpickr-months .flatpickr-prev-month.disabled,.flatpickr-months .flatpickr-next-month.disabled{display:none}.flatpickr-months .flatpickr-prev-month i,.flatpickr-months .flatpickr-next-month i{position:relative}.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,.flatpickr-months .flatpickr-next-month.flatpickr-prev-month{/* + /*rtl:begin:ignore*/left:0;/* + /*rtl:end:ignore*/}/* + /*rtl:begin:ignore*/ +/* + /*rtl:end:ignore*/ +.flatpickr-months .flatpickr-prev-month.flatpickr-next-month,.flatpickr-months .flatpickr-next-month.flatpickr-next-month{/* + /*rtl:begin:ignore*/right:0;/* + /*rtl:end:ignore*/}/* + /*rtl:begin:ignore*/ +/* + /*rtl:end:ignore*/ +.flatpickr-months .flatpickr-prev-month:hover,.flatpickr-months .flatpickr-next-month:hover{color:#959ea9;}.flatpickr-months .flatpickr-prev-month:hover svg,.flatpickr-months .flatpickr-next-month:hover svg{fill:#f64747}.flatpickr-months .flatpickr-prev-month svg,.flatpickr-months .flatpickr-next-month svg{width:14px;height:14px;}.flatpickr-months .flatpickr-prev-month svg path,.flatpickr-months .flatpickr-next-month svg path{-webkit-transition:fill .1s;transition:fill .1s;fill:inherit}.numInputWrapper{position:relative;height:auto;}.numInputWrapper input,.numInputWrapper span{display:inline-block}.numInputWrapper input{width:100%;}.numInputWrapper input::-ms-clear{display:none}.numInputWrapper span{position:absolute;right:0;width:14px;padding:0 4px 0 2px;height:50%;line-height:50%;opacity:0;cursor:pointer;border:1px solid rgba(57,57,57,0.15);-webkit-box-sizing:border-box;box-sizing:border-box;}.numInputWrapper span:hover{background:rgba(0,0,0,0.1)}.numInputWrapper span:active{background:rgba(0,0,0,0.2)}.numInputWrapper span:after{display:block;content:"";position:absolute}.numInputWrapper span.arrowUp{top:0;border-bottom:0;}.numInputWrapper span.arrowUp:after{border-left:4px solid transparent;border-right:4px solid transparent;border-bottom:4px solid rgba(57,57,57,0.6);top:26%}.numInputWrapper span.arrowDown{top:50%;}.numInputWrapper span.arrowDown:after{border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid rgba(57,57,57,0.6);top:40%}.numInputWrapper span svg{width:inherit;height:auto;}.numInputWrapper span svg path{fill:rgba(0,0,0,0.5)}.numInputWrapper:hover{background:rgba(0,0,0,0.05);}.numInputWrapper:hover span{opacity:1}.flatpickr-current-month{font-size:135%;line-height:inherit;font-weight:300;color:inherit;position:absolute;width:75%;left:12.5%;padding:6.16px 0 0 0;line-height:1;height:28px;display:inline-block;text-align:center;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}.flatpickr-current-month span.cur-month{font-family:inherit;font-weight:700;color:inherit;display:inline-block;margin-left:.5ch;padding:0;}.flatpickr-current-month span.cur-month:hover{background:rgba(0,0,0,0.05)}.flatpickr-current-month .numInputWrapper{width:6ch;width:7ch\0;display:inline-block;}.flatpickr-current-month .numInputWrapper span.arrowUp:after{border-bottom-color:rgba(0,0,0,0.9)}.flatpickr-current-month .numInputWrapper span.arrowDown:after{border-top-color:rgba(0,0,0,0.9)}.flatpickr-current-month input.cur-year{background:transparent;-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;cursor:text;padding:0 0 0 .5ch;margin:0;display:inline-block;font-size:inherit;font-family:inherit;font-weight:300;line-height:inherit;height:auto;border:0;border-radius:0;vertical-align:initial;}.flatpickr-current-month input.cur-year:focus{outline:0}.flatpickr-current-month input.cur-year[disabled],.flatpickr-current-month input.cur-year[disabled]:hover{font-size:100%;color:rgba(0,0,0,0.5);background:transparent;pointer-events:none}.flatpickr-weekdays{background:transparent;text-align:center;overflow:hidden;width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:28px;}.flatpickr-weekdays .flatpickr-weekdaycontainer{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}span.flatpickr-weekday{cursor:default;font-size:90%;background:transparent;color:rgba(0,0,0,0.54);line-height:1;margin:0;text-align:center;display:block;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;font-weight:bolder}.dayContainer,.flatpickr-weeks{padding:1px 0 0 0}.flatpickr-days{position:relative;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:start;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;width:307.875px;}.flatpickr-days:focus{outline:0}.dayContainer{padding:0;outline:0;text-align:left;width:307.875px;min-width:307.875px;max-width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;display:inline-block;display:-ms-flexbox;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-wrap:wrap;-ms-flex-pack:justify;-webkit-justify-content:space-around;justify-content:space-around;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1;}.dayContainer + .dayContainer{-webkit-box-shadow:-1px 0 0 #e6e6e6;box-shadow:-1px 0 0 #e6e6e6}.flatpickr-day{background:none;border:1px solid transparent;border-radius:150px;-webkit-box-sizing:border-box;box-sizing:border-box;color:#393939;cursor:pointer;font-weight:400;width:14.2857143%;-webkit-flex-basis:14.2857143%;-ms-flex-preferred-size:14.2857143%;flex-basis:14.2857143%;max-width:39px;height:39px;line-height:39px;margin:0;display:inline-block;position:relative;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;}.flatpickr-day.inRange,.flatpickr-day.prevMonthDay.inRange,.flatpickr-day.nextMonthDay.inRange,.flatpickr-day.today.inRange,.flatpickr-day.prevMonthDay.today.inRange,.flatpickr-day.nextMonthDay.today.inRange,.flatpickr-day:hover,.flatpickr-day.prevMonthDay:hover,.flatpickr-day.nextMonthDay:hover,.flatpickr-day:focus,.flatpickr-day.prevMonthDay:focus,.flatpickr-day.nextMonthDay:focus{cursor:pointer;outline:0;background:#e6e6e6;border-color:#e6e6e6}.flatpickr-day.today{border-color:#959ea9;}.flatpickr-day.today:hover,.flatpickr-day.today:focus{border-color:#959ea9;background:#959ea9;color:#fff}.flatpickr-day.selected,.flatpickr-day.startRange,.flatpickr-day.endRange,.flatpickr-day.selected.inRange,.flatpickr-day.startRange.inRange,.flatpickr-day.endRange.inRange,.flatpickr-day.selected:focus,.flatpickr-day.startRange:focus,.flatpickr-day.endRange:focus,.flatpickr-day.selected:hover,.flatpickr-day.startRange:hover,.flatpickr-day.endRange:hover,.flatpickr-day.selected.prevMonthDay,.flatpickr-day.startRange.prevMonthDay,.flatpickr-day.endRange.prevMonthDay,.flatpickr-day.selected.nextMonthDay,.flatpickr-day.startRange.nextMonthDay,.flatpickr-day.endRange.nextMonthDay{background:#569ff7;-webkit-box-shadow:none;box-shadow:none;color:#fff;border-color:#569ff7}.flatpickr-day.selected.startRange,.flatpickr-day.startRange.startRange,.flatpickr-day.endRange.startRange{border-radius:50px 0 0 50px}.flatpickr-day.selected.endRange,.flatpickr-day.startRange.endRange,.flatpickr-day.endRange.endRange{border-radius:0 50px 50px 0}.flatpickr-day.selected.startRange + .endRange,.flatpickr-day.startRange.startRange + .endRange,.flatpickr-day.endRange.startRange + .endRange{-webkit-box-shadow:-10px 0 0 #569ff7;box-shadow:-10px 0 0 #569ff7}.flatpickr-day.selected.startRange.endRange,.flatpickr-day.startRange.startRange.endRange,.flatpickr-day.endRange.startRange.endRange{border-radius:50px}.flatpickr-day.inRange{border-radius:0;-webkit-box-shadow:-5px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-5px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-day.disabled,.flatpickr-day.disabled:hover,.flatpickr-day.prevMonthDay,.flatpickr-day.nextMonthDay,.flatpickr-day.notAllowed,.flatpickr-day.notAllowed.prevMonthDay,.flatpickr-day.notAllowed.nextMonthDay{color:rgba(57,57,57,0.3);background:transparent;border-color:transparent;cursor:default}.flatpickr-day.disabled,.flatpickr-day.disabled:hover{cursor:not-allowed;color:rgba(57,57,57,0.1)}.flatpickr-day.week.selected{border-radius:0;-webkit-box-shadow:-5px 0 0 #569ff7,5px 0 0 #569ff7;box-shadow:-5px 0 0 #569ff7,5px 0 0 #569ff7}.flatpickr-day.hidden{visibility:hidden}.rangeMode .flatpickr-day{margin-top:1px}.flatpickr-weekwrapper{display:inline-block;float:left;}.flatpickr-weekwrapper .flatpickr-weeks{padding:0 12px;-webkit-box-shadow:1px 0 0 #e6e6e6;box-shadow:1px 0 0 #e6e6e6}.flatpickr-weekwrapper .flatpickr-weekday{float:none;width:100%;line-height:28px}.flatpickr-weekwrapper span.flatpickr-day,.flatpickr-weekwrapper span.flatpickr-day:hover{display:block;width:100%;max-width:none;color:rgba(57,57,57,0.3);background:transparent;cursor:default;border:none}.flatpickr-innerContainer{display:block;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;}.flatpickr-rContainer{display:inline-block;padding:0;-webkit-box-sizing:border-box;box-sizing:border-box}.flatpickr-time{text-align:center;outline:0;display:block;height:0;line-height:40px;max-height:40px;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}.flatpickr-time:after{content:"";display:table;clear:both}.flatpickr-time .numInputWrapper{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;width:40%;height:40px;float:left;}.flatpickr-time .numInputWrapper span.arrowUp:after{border-bottom-color:#393939}.flatpickr-time .numInputWrapper span.arrowDown:after{border-top-color:#393939}.flatpickr-time.hasSeconds .numInputWrapper{width:26%}.flatpickr-time.time24hr .numInputWrapper{width:49%}.flatpickr-time input{background:transparent;-webkit-box-shadow:none;box-shadow:none;border:0;border-radius:0;text-align:center;margin:0;padding:0;height:inherit;line-height:inherit;cursor:pointer;color:#393939;font-size:14px;position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;}.flatpickr-time input.flatpickr-hour{font-weight:bold}.flatpickr-time input.flatpickr-minute,.flatpickr-time input.flatpickr-second{font-weight:400}.flatpickr-time input:focus{outline:0;border:0}.flatpickr-time .flatpickr-time-separator,.flatpickr-time .flatpickr-am-pm{height:inherit;display:inline-block;float:left;line-height:inherit;color:#393939;font-weight:bold;width:2%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.flatpickr-time .flatpickr-am-pm{outline:0;width:18%;cursor:pointer;text-align:center;font-weight:400;}.flatpickr-time .flatpickr-am-pm:hover,.flatpickr-time .flatpickr-am-pm:focus{background:#f0f0f0}.flatpickr-input[readonly]{cursor:pointer}@-webkit-keyframes fpFadeInDown{from{opacity:0;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:1;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes fpFadeInDown{from{opacity:0;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:1;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}} diff --git a/static/js/flatpickr.js b/static/js/flatpickr.js new file mode 100644 index 000000000..226f63df3 --- /dev/null +++ b/static/js/flatpickr.js @@ -0,0 +1,2 @@ +/* flatpickr v4.4.4,, @license MIT */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.flatpickr=t()}(this,function(){"use strict";var Z=function(e){return("0"+e).slice(-2)},Q=function(e){return!0===e?1:0};function X(n,a,i){var o;return void 0===i&&(i=!1),function(){var e=this,t=arguments;null!==o&&clearTimeout(o),o=window.setTimeout(function(){o=null,i||n.apply(e,t)},a),i&&!o&&n.apply(e,t)}}var ee=function(e){return e instanceof Array?e:[e]},e=function(){},te=function(e,t,n){return n.months[t?"shorthand":"longhand"][e]},D={D:e,F:function(e,t,n){e.setMonth(n.months.longhand.indexOf(t))},G:function(e,t){e.setHours(parseFloat(t))},H:function(e,t){e.setHours(parseFloat(t))},J:function(e,t){e.setDate(parseFloat(t))},K:function(e,t,n){e.setHours(e.getHours()%12+12*Q(new RegExp(n.amPM[1],"i").test(t)))},M:function(e,t,n){e.setMonth(n.months.shorthand.indexOf(t))},S:function(e,t){e.setSeconds(parseFloat(t))},U:function(e,t){return new Date(1e3*parseFloat(t))},W:function(e,t){var n=parseInt(t);return new Date(e.getFullYear(),0,2+7*(n-1),0,0,0,0)},Y:function(e,t){e.setFullYear(parseFloat(t))},Z:function(e,t){return new Date(t)},d:function(e,t){e.setDate(parseFloat(t))},h:function(e,t){e.setHours(parseFloat(t))},i:function(e,t){e.setMinutes(parseFloat(t))},j:function(e,t){e.setDate(parseFloat(t))},l:e,m:function(e,t){e.setMonth(parseFloat(t)-1)},n:function(e,t){e.setMonth(parseFloat(t)-1)},s:function(e,t){e.setSeconds(parseFloat(t))},w:e,y:function(e,t){e.setFullYear(2e3+parseFloat(t))}},ne={D:"(\\w+)",F:"(\\w+)",G:"(\\d\\d|\\d)",H:"(\\d\\d|\\d)",J:"(\\d\\d|\\d)\\w+",K:"",M:"(\\w+)",S:"(\\d\\d|\\d)",U:"(.+)",W:"(\\d\\d|\\d)",Y:"(\\d{4})",Z:"(.+)",d:"(\\d\\d|\\d)",h:"(\\d\\d|\\d)",i:"(\\d\\d|\\d)",j:"(\\d\\d|\\d)",l:"(\\w+)",m:"(\\d\\d|\\d)",n:"(\\d\\d|\\d)",s:"(\\d\\d|\\d)",w:"(\\d\\d|\\d)",y:"(\\d{2})"},c={Z:function(e){return e.toISOString()},D:function(e,t,n){return t.weekdays.shorthand[c.w(e,t,n)]},F:function(e,t,n){return te(c.n(e,t,n)-1,!1,t)},G:function(e,t,n){return Z(c.h(e,t,n))},H:function(e){return Z(e.getHours())},J:function(e,t){return void 0!==t.ordinal?e.getDate()+t.ordinal(e.getDate()):e.getDate()},K:function(e,t){return t.amPM[Q(11Math.min(t,n)&&e",noCalendar:!1,now:new Date,onChange:[],onClose:[],onDayCreate:[],onDestroy:[],onKeyDown:[],onMonthChange:[],onOpen:[],onParseConfig:[],onReady:[],onValueUpdate:[],onYearChange:[],onPreCalendarPosition:[],plugins:[],position:"auto",positionElement:void 0,prevArrow:"",shorthandCurrentMonth:!1,showMonths:1,static:!1,time_24hr:!1,weekNumbers:!1,wrap:!1};function de(e,t,n){if(!0===n)return e.classList.add(t);e.classList.remove(t)}function se(e,t,n){var a=window.document.createElement(e);return t=t||"",n=n||"",a.className=t,void 0!==n&&(a.textContent=n),a}function ue(e){for(;e.firstChild;)e.removeChild(e.firstChild)}function fe(e,t){var n=se("div","numInputWrapper"),a=se("input","numInput "+e),i=se("span","arrowUp"),o=se("span","arrowDown");if(a.type="text",a.pattern="\\d*",void 0!==t)for(var r in t)a.setAttribute(r,t[r]);return n.appendChild(a),n.appendChild(i),n.appendChild(o),n}"function"!=typeof Object.assign&&(Object.assign=function(n){if(!n)throw TypeError("Cannot convert undefined or null to object");for(var e=arguments.length,t=new Array(1o)&&(D.amPM.textContent=D.l10n.amPM[Q(D.amPM.textContent===D.l10n.amPM[0])]),n.value=Z(l)}}(e),"input"!==e.type?(m(),G()):setTimeout(function(){m(),G()},me))}function m(){if(void 0!==D.hourElement&&void 0!==D.minuteElement){var e,t,n=(parseInt(D.hourElement.value.slice(-2),10)||0)%24,a=(parseInt(D.minuteElement.value,10)||0)%60,i=void 0!==D.secondElement?(parseInt(D.secondElement.value,10)||0)%60:0;void 0!==D.amPM&&(e=n,t=D.amPM.textContent,n=e%12+12*Q(t===D.l10n.amPM[1]));var o=void 0!==D.config.minTime||D.config.minDate&&D.minDateHasTime&&D.latestSelectedDateObj&&0===re(D.latestSelectedDateObj,D.config.minDate,!0);if(void 0!==D.config.maxTime||D.config.maxDate&&D.maxDateHasTime&&D.latestSelectedDateObj&&0===re(D.latestSelectedDateObj,D.config.maxDate,!0)){var r=void 0!==D.config.maxTime?D.config.maxTime:D.config.maxDate;(n=Math.min(n,r.getHours()))===r.getHours()&&(a=Math.min(a,r.getMinutes())),a===r.getMinutes()&&(i=Math.min(i,r.getSeconds()))}if(o){var c=void 0!==D.config.minTime?D.config.minTime:D.config.minDate;(n=Math.max(n,c.getHours()))===c.getHours()&&(a=Math.max(a,c.getMinutes())),a===c.getMinutes()&&(i=Math.max(i,c.getSeconds()))}l(n,a,i)}}function g(e){var t=e||D.latestSelectedDateObj;t&&l(t.getHours(),t.getMinutes(),t.getSeconds())}function l(e,t,n){void 0!==D.latestSelectedDateObj&&D.latestSelectedDateObj.setHours(e%24,t,n||0,0),D.hourElement&&D.minuteElement&&!D.isMobile&&(D.hourElement.value=Z(D.config.time_24hr?e:(12+e)%12+12*Q(e%12==0)),D.minuteElement.value=Z(t),void 0!==D.amPM&&(D.amPM.textContent=D.l10n.amPM[Q(12<=e)]),void 0!==D.secondElement&&(D.secondElement.value=Z(n)))}function n(e){var t=parseInt(e.target.value)+(e.delta||0);4!==t.toString().length&&"Enter"!==e.key||(e.target.blur(),/[^\d]/.test(t.toString())||S(t))}function o(t,n,a,i){return n instanceof Array?n.forEach(function(e){return o(t,e,a,i)}):t instanceof Array?t.forEach(function(e){return o(e,n,a,i)}):(t.addEventListener(n,a,i),void D._handlers.push({element:t,event:n,handler:a}))}function a(t){return function(e){1===e.which&&t(e)}}function p(){U("onChange")}function i(e){var t=void 0!==e?D.parseDate(e):D.latestSelectedDateObj||(D.config.minDate&&D.config.minDate>D.now?D.config.minDate:D.config.maxDate&&D.config.maxDate"+D.config.getWeek(t)+""),U("onDayCreate",r),r}function b(e){e.focus(),"range"===D.config.mode&&A(e)}function w(e){for(var t=0=Math.abs(t))return b(s)}D.changeMonth(i),M(w(i),0)}(a,t):b(a)}function c(e,t){for(var n=(new Date(e,t,1).getDay()-D.l10n.firstDayOfWeek+7)%7,a=D.utils.getDaysInMonth((t-1+12)%12),i=D.utils.getDaysInMonth(t),o=window.document.createDocumentFragment(),r=1\n "+t.join("")+"\n \n "}function k(e,t){void 0===t&&(t=!0);var n=t?e:e-D.currentMonth;n<0&&!0===D._hidePrevMonthArrow||0D.config.maxDate.getFullYear())){var t=e,n=D.currentYear!==t;D.currentYear=t||D.currentYear,D.config.maxDate&&D.currentYear===D.config.maxDate.getFullYear()?D.currentMonth=Math.min(D.config.maxDate.getMonth(),D.currentMonth):D.config.minDate&&D.currentYear===D.config.minDate.getFullYear()&&(D.currentMonth=Math.max(D.config.minDate.getMonth(),D.currentMonth)),n&&(D.redraw(),U("onYearChange"))}}function P(e,t){void 0===t&&(t=!0);var n=D.parseDate(e,void 0,t);if(D.config.minDate&&n&&re(n,D.config.minDate,void 0!==t?t:!D.minDateHasTime)<0||D.config.maxDate&&n&&0=a.from.getTime()&&n.getTime()<=a.to.getTime())return i}return!i}function _(e){return void 0!==D.daysContainer&&(-1===e.className.indexOf("hidden")&&D.daysContainer.contains(e))}function F(e){e.stopPropagation();var t=e.target===D._input,n=I(e.target),a=D.config.allowInput,i=D.isOpen&&(!a||!t),o=D.config.inline&&t&&!a;if(13===e.keyCode&&t){if(a)return D.setDate(D._input.value,!0,e.target===D.altInput?D.config.altFormat:D.config.dateFormat),e.target.blur();D.open()}else if(n||i||o){var r=!!D.timeContainer&&D.timeContainer.contains(e.target);switch(e.keyCode){case 13:r?G():R(e);break;case 27:e.preventDefault(),W();break;case 8:case 46:t&&!D.config.allowInput&&(e.preventDefault(),D.clear());break;case 37:case 39:if(r)D.hourElement&&D.hourElement.focus();else if(e.preventDefault(),void 0!==D.daysContainer&&!1===D.config.allowInput){var c=39===e.keyCode?1:-1;e.ctrlKey?(k(c),M(w(1),0)):M(void 0,c)}break;case 38:case 40:e.preventDefault();var l=40===e.keyCode?1:-1;D.daysContainer?e.ctrlKey?(S(D.currentYear-l),M(w(1),0)):r||M(void 0,7*l):D.config.enableTime&&(!r&&D.hourElement&&D.hourElement.focus(),d(e),D._debouncedChange());break;case 9:e.target===D.hourElement?(e.preventDefault(),D.minuteElement.select()):e.target===D.minuteElement&&(D.secondElement||D.amPM)?(e.preventDefault(),void 0!==D.secondElement?D.secondElement.focus():void 0!==D.amPM&&D.amPM.focus()):e.target===D.secondElement&&D.amPM&&(e.preventDefault(),D.amPM.focus())}switch(e.key){case D.l10n.amPM[0].charAt(0):case D.l10n.amPM[0].charAt(0).toLowerCase():void 0!==D.amPM&&e.target===D.amPM&&(D.amPM.textContent=D.l10n.amPM[0],m(),G());break;case D.l10n.amPM[1].charAt(0):case D.l10n.amPM[1].charAt(0).toLowerCase():void 0!==D.amPM&&e.target===D.amPM&&(D.amPM.textContent=D.l10n.amPM[1],m(),G())}U("onKeyDown",e)}}function A(o){if(1===D.selectedDates.length&&o.classList.contains("flatpickr-day")&&!o.classList.contains("disabled")){for(var r=o.dateObj.getTime(),c=D.parseDate(D.selectedDates[0],void 0,!0).getTime(),e=Math.min(r,D.selectedDates[0].getTime()),t=Math.max(r,D.selectedDates[0].getTime()),n=D.daysContainer.children,a=n[0].children[0].dateObj.getTime(),i=n[n.length-1].lastChild.dateObj.getTime(),l=!1,d=0,s=0,u=a;u=a||(cn,l=window.pageYOffset+o.top+(c?-n-2:t.offsetHeight+2);if(de(D.calendarContainer,"arrowTop",!c),de(D.calendarContainer,"arrowBottom",c),!D.config.inline){var d=window.pageXOffset+o.left,s=window.document.body.offsetWidth-o.right,u=d+a>window.document.body.offsetWidth;de(D.calendarContainer,"rightMost",u),D.config.static||(D.calendarContainer.style.top=l+"px",u?(D.calendarContainer.style.left="auto",D.calendarContainer.style.right=s+"px"):(D.calendarContainer.style.left=d+"px",D.calendarContainer.style.right="auto"))}}}function L(){D.config.noCalendar||D.isMobile||(z(),C())}function W(){D._input.focus(),-1!==window.navigator.userAgent.indexOf("MSIE")||void 0!==navigator.msMaxTouchPoints?setTimeout(D.close,0):D.close()}function R(e){e.preventDefault(),e.stopPropagation();var t=function e(t,n){return n(t)?t:t.parentNode?e(t.parentNode,n):void 0}(e.target,function(e){return e.classList&&e.classList.contains("flatpickr-day")&&!e.classList.contains("disabled")&&!e.classList.contains("notAllowed")});if(void 0!==t){var n=t,a=D.latestSelectedDateObj=new Date(n.dateObj.getTime()),i=(a.getMonth()D.currentMonth+D.config.showMonths-1)&&"range"!==D.config.mode;if(D.selectedDateElem=n,"single"===D.config.mode)D.selectedDates=[a];else if("multiple"===D.config.mode){var o=$(a);o?D.selectedDates.splice(parseInt(o),1):D.selectedDates.push(a)}else"range"===D.config.mode&&(2===D.selectedDates.length&&D.clear(!1),D.selectedDates.push(a),0!==re(a,D.selectedDates[0],!0)&&D.selectedDates.sort(function(e,t){return e.getTime()-t.getTime()}));if(m(),i){var r=D.currentYear!==a.getFullYear();D.currentYear=a.getFullYear(),D.currentMonth=a.getMonth(),r&&U("onYearChange"),U("onMonthChange")}if(z(),C(),D.config.minDate&&D.minDateHasTime&&D.config.enableTime&&0===re(a,D.config.minDate)&&g(D.config.minDate),G(),D.config.enableTime&&setTimeout(function(){return D.showTimeInput=!0},50),"range"===D.config.mode&&(1===D.selectedDates.length?A(n):z()),i||"range"===D.config.mode||1!==D.config.showMonths?D.selectedDateElem&&D.selectedDateElem.focus():b(n),void 0!==D.hourElement&&setTimeout(function(){return void 0!==D.hourElement&&D.hourElement.select()},451),D.config.closeOnSelect){var c="single"===D.config.mode&&!D.config.enableTime,l="range"===D.config.mode&&2===D.selectedDates.length&&!D.config.enableTime;(c||l)&&W()}p()}}D.parseDate=oe({config:D.config,l10n:D.l10n}),D._handlers=[],D._bind=o,D._setHoursFromDate=g,D.changeMonth=k,D.changeYear=S,D.clear=function(e){void 0===e&&(e=!0);D.input.value="",void 0!==D.altInput&&(D.altInput.value="");void 0!==D.mobileInput&&(D.mobileInput.value="");D.selectedDates=[],D.latestSelectedDateObj=void 0,!(D.showTimeInput=!1)===D.config.enableTime&&(void 0!==D.config.minDate?g(D.config.minDate):l(D.config.defaultHour,D.config.defaultMinute,D.config.defaultSeconds));D.redraw(),e&&U("onChange")},D.close=function(){D.isOpen=!1,D.isMobile||(D.calendarContainer.classList.remove("open"),D._input.classList.remove("active"));U("onClose")},D._createElement=se,D.destroy=function(){void 0!==D.config&&U("onDestroy");for(var e=D._handlers.length;e--;){var t=D._handlers[e];t.element.removeEventListener(t.event,t.handler)}D._handlers=[],D.mobileInput?(D.mobileInput.parentNode&&D.mobileInput.parentNode.removeChild(D.mobileInput),D.mobileInput=void 0):D.calendarContainer&&D.calendarContainer.parentNode&&D.calendarContainer.parentNode.removeChild(D.calendarContainer);D.altInput&&(D.input.type="text",D.altInput.parentNode&&D.altInput.parentNode.removeChild(D.altInput),delete D.altInput);D.input&&(D.input.type=D.input._type,D.input.classList.remove("flatpickr-input"),D.input.removeAttribute("readonly"),D.input.value="");["_showTimeInput","latestSelectedDateObj","_hideNextMonthArrow","_hidePrevMonthArrow","__hideNextMonthArrow","__hidePrevMonthArrow","isMobile","isOpen","selectedDateElem","minDateHasTime","maxDateHasTime","days","daysContainer","_input","_positionElement","innerContainer","rContainer","monthNav","todayDateElem","calendarContainer","weekdayContainer","prevMonthNav","nextMonthNav","currentMonthElement","currentYearElement","navigationCurrentMonth","selectedDateElem","config"].forEach(function(e){try{delete D[e]}catch(e){}})},D.isEnabled=P,D.jumpToDate=i,D.open=function(e,t){void 0===t&&(t=D._input);if(!0===D.isMobile)return e&&(e.preventDefault(),e.target&&e.target.blur()),setTimeout(function(){void 0!==D.mobileInput&&D.mobileInput.click()},0),void U("onOpen");if(D._input.disabled||D.config.inline)return;var n=D.isOpen;D.isOpen=!0,n||(D.calendarContainer.classList.add("open"),D._input.classList.add("active"),U("onOpen"),H(t));!0===D.config.enableTime&&!0===D.config.noCalendar&&(0===D.selectedDates.length&&(D.setDate(void 0!==D.config.minDate?new Date(D.config.minDate.getTime()):(new Date).setHours(D.config.defaultHour,D.config.defaultMinute,D.config.defaultSeconds,0),!1),m(),G()),setTimeout(function(){return D.hourElement.select()},50))},D.redraw=L,D.set=function(e,t){null!==e&&"object"==typeof e?Object.assign(D.config,e):(D.config[e]=t,void 0!==J[e]&&J[e].forEach(function(e){return e()}));D.redraw(),i()},D.setDate=function(e,t,n){void 0===t&&(t=!1);void 0===n&&(n=D.config.dateFormat);if(0!==e&&!e)return D.clear(t);K(e,n),D.showTimeInput=0D.config.maxDate.getMonth():D.currentYear>D.config.maxDate.getFullYear()))}function G(e){if(void 0===e&&(e=!0),0===D.selectedDates.length)return D.clear(e);void 0!==D.mobileInput&&D.mobileFormatStr&&(D.mobileInput.value=void 0!==D.latestSelectedDateObj?D.formatDate(D.latestSelectedDateObj,D.mobileFormatStr):"");var t="range"!==D.config.mode?D.config.conjunction:D.l10n.rangeSeparator;D.input.value=D.selectedDates.map(function(e){return D.formatDate(e,D.config.dateFormat)}).join(t),void 0!==D.altInput&&(D.altInput.value=D.selectedDates.map(function(e){return D.formatDate(e,D.config.altFormat)}).join(t)),!1!==e&&U("onValueUpdate")}function V(e){var t=D.prevMonthNav.contains(e.target),n=D.nextMonthNav.contains(e.target);t||n?k(t?-1:1):0<=D.yearElements.indexOf(e.target)?(e.preventDefault(),e.target.select()):e.target.classList.contains("arrowUp")?D.changeYear(D.currentYear+1):e.target.classList.contains("arrowDown")&&D.changeYear(D.currentYear-1)}return function(){D.element=D.input=s,D.isOpen=!1,function(){var e=["wrap","weekNumbers","allowInput","clickOpens","time_24hr","enableTime","noCalendar","altInput","shorthandCurrentMonth","inline","static","enableSeconds","disableMobile"],t=["onChange","onClose","onDayCreate","onDestroy","onKeyDown","onMonthChange","onOpen","onParseConfig","onReady","onValueUpdate","onYearChange","onPreCalendarPosition"],n=Object.assign({},u,JSON.parse(JSON.stringify(s.dataset||{}))),a={};D.config.parseDate=n.parseDate,D.config.formatDate=n.formatDate,Object.defineProperty(D.config,"enable",{get:function(){return D.config._enable},set:function(e){D.config._enable=B(e)}}),Object.defineProperty(D.config,"disable",{get:function(){return D.config._disable},set:function(e){D.config._disable=B(e)}}),!n.dateFormat&&n.enableTime&&(a.dateFormat=n.noCalendar?"H:i"+(n.enableSeconds?":S":""):ge.defaultConfig.dateFormat+" H:i"+(n.enableSeconds?":S":"")),n.altInput&&n.enableTime&&!n.altFormat&&(a.altFormat=n.noCalendar?"h:i"+(n.enableSeconds?":S K":" K"):ge.defaultConfig.altFormat+" h:i"+(n.enableSeconds?":S":"")+" K"),Object.defineProperty(D.config,"minDate",{get:function(){return D.config._minDate},set:j("min")}),Object.defineProperty(D.config,"maxDate",{get:function(){return D.config._maxDate},set:j("max")});var i=function(t){return function(e){D.config["min"===t?"_minTime":"_maxTime"]=D.parseDate(e,"H:i")}};Object.defineProperty(D.config,"minTime",{get:function(){return D.config._minTime},set:i("min")}),Object.defineProperty(D.config,"maxTime",{get:function(){return D.config._maxTime},set:i("max")}),Object.assign(D.config,a,n);for(var o=0;oD.now.getTime()?D.config.minDate:D.config.maxDate&&D.config.maxDate.getTime() Date: Mon, 16 Apr 2018 20:59:19 +0200 Subject: [PATCH 007/559] example inputs on homepage --- static/css/flatpickr.css | 741 ++++++++++++++++++++++++++++++++++++++- templates/home.hamlet | 40 +-- templates/home.julius | 19 + 3 files changed, 759 insertions(+), 41 deletions(-) diff --git a/static/css/flatpickr.css b/static/css/flatpickr.css index 9d24b0240..2e9c517e9 100644 --- a/static/css/flatpickr.css +++ b/static/css/flatpickr.css @@ -1,13 +1,740 @@ -.flatpickr-calendar{background:transparent;opacity:0;display:none;text-align:center;visibility:hidden;padding:0;-webkit-animation:none;animation:none;direction:ltr;border:0;font-size:14px;line-height:24px;border-radius:5px;position:absolute;width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-touch-action:manipulation;touch-action:manipulation;background:#fff;-webkit-box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);}.flatpickr-calendar.open,.flatpickr-calendar.inline{opacity:1;max-height:640px;visibility:visible}.flatpickr-calendar.open{display:inline-block;z-index:99999}.flatpickr-calendar.animate.open{-webkit-animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1);animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1)}.flatpickr-calendar.inline{display:block;position:relative;top:2px}.flatpickr-calendar.static{position:absolute;top:calc(100% + 2px);}.flatpickr-calendar.static.open{z-index:999;display:block}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7){-webkit-box-shadow:none !important;box-shadow:none !important}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1){-webkit-box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-calendar .hasWeeks .dayContainer,.flatpickr-calendar .hasTime .dayContainer{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.flatpickr-calendar .hasWeeks .dayContainer{border-left:0}.flatpickr-calendar.showTimeInput.hasTime .flatpickr-time{height:40px;border-top:1px solid #e6e6e6}.flatpickr-calendar.noCalendar.hasTime .flatpickr-time{height:auto}.flatpickr-calendar:before,.flatpickr-calendar:after{position:absolute;display:block;pointer-events:none;border:solid transparent;content:'';height:0;width:0;left:22px}.flatpickr-calendar.rightMost:before,.flatpickr-calendar.rightMost:after{left:auto;right:22px}.flatpickr-calendar:before{border-width:5px;margin:0 -5px}.flatpickr-calendar:after{border-width:4px;margin:0 -4px}.flatpickr-calendar.arrowTop:before,.flatpickr-calendar.arrowTop:after{bottom:100%}.flatpickr-calendar.arrowTop:before{border-bottom-color:#e6e6e6}.flatpickr-calendar.arrowTop:after{border-bottom-color:#fff}.flatpickr-calendar.arrowBottom:before,.flatpickr-calendar.arrowBottom:after{top:100%}.flatpickr-calendar.arrowBottom:before{border-top-color:#e6e6e6}.flatpickr-calendar.arrowBottom:after{border-top-color:#fff}.flatpickr-calendar:focus{outline:0}.flatpickr-wrapper{position:relative;display:inline-block}.flatpickr-months{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}.flatpickr-months .flatpickr-month{background:transparent;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9);height:28px;line-height:1;text-align:center;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:hidden;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.flatpickr-months .flatpickr-prev-month,.flatpickr-months .flatpickr-next-month{text-decoration:none;cursor:pointer;position:absolute;top:0;line-height:16px;height:28px;padding:10px;z-index:3;}.flatpickr-months .flatpickr-prev-month.disabled,.flatpickr-months .flatpickr-next-month.disabled{display:none}.flatpickr-months .flatpickr-prev-month i,.flatpickr-months .flatpickr-next-month i{position:relative}.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,.flatpickr-months .flatpickr-next-month.flatpickr-prev-month{/* - /*rtl:begin:ignore*/left:0;/* - /*rtl:end:ignore*/}/* +.flatpickr-calendar { + background: transparent; + opacity: 0; + display: none; + text-align: center; + visibility: hidden; + padding: 0; + -webkit-animation: none; + animation: none; + direction: ltr; + border: 0; + font-size: 14px; + line-height: 24px; + border-radius: 5px; + position: absolute; + width: 307.875px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + -ms-touch-action: manipulation; + touch-action: manipulation; + background: #fff; + -webkit-box-shadow: 1px 0 0 #e6e6e6, -1px 0 0 #e6e6e6, 0 1px 0 #e6e6e6, 0 -1px 0 #e6e6e6, 0 3px 13px rgba(0,0,0,0.08); + box-shadow: 1px 0 0 #e6e6e6, -1px 0 0 #e6e6e6, 0 1px 0 #e6e6e6, 0 -1px 0 #e6e6e6, 0 3px 13px rgba(0,0,0,0.08); +} +.flatpickr-calendar.open, +.flatpickr-calendar.inline { + opacity: 1; + max-height: 640px; + visibility: visible; +} +.flatpickr-calendar.open { + display: inline-block; + z-index: 99999; +} +.flatpickr-calendar.animate.open { + -webkit-animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1); + animation: fpFadeInDown 300ms cubic-bezier(0.23, 1, 0.32, 1); +} +.flatpickr-calendar.inline { + display: block; + position: relative; + top: 2px; +} +.flatpickr-calendar.static { + position: absolute; + top: calc(100% + 2px); +} +.flatpickr-calendar.static.open { + z-index: 999; + display: block; +} +.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7) { + -webkit-box-shadow: none !important; + box-shadow: none !important; +} +.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1) { + -webkit-box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; + box-shadow: -2px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; +} +.flatpickr-calendar .hasWeeks .dayContainer, +.flatpickr-calendar .hasTime .dayContainer { + border-bottom: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.flatpickr-calendar .hasWeeks .dayContainer { + border-left: 0; +} +.flatpickr-calendar.showTimeInput.hasTime .flatpickr-time { + height: 40px; + border-top: 1px solid #e6e6e6; +} +.flatpickr-calendar.noCalendar.hasTime .flatpickr-time { + height: auto; +} +.flatpickr-calendar:before, +.flatpickr-calendar:after { + position: absolute; + display: block; + pointer-events: none; + border: solid transparent; + content: ''; + height: 0; + width: 0; + left: 22px; +} +.flatpickr-calendar.rightMost:before, +.flatpickr-calendar.rightMost:after { + left: auto; + right: 22px; +} +.flatpickr-calendar:before { + border-width: 5px; + margin: 0 -5px; +} +.flatpickr-calendar:after { + border-width: 4px; + margin: 0 -4px; +} +.flatpickr-calendar.arrowTop:before, +.flatpickr-calendar.arrowTop:after { + bottom: 100%; +} +.flatpickr-calendar.arrowTop:before { + border-bottom-color: #e6e6e6; +} +.flatpickr-calendar.arrowTop:after { + border-bottom-color: #fff; +} +.flatpickr-calendar.arrowBottom:before, +.flatpickr-calendar.arrowBottom:after { + top: 100%; +} +.flatpickr-calendar.arrowBottom:before { + border-top-color: #e6e6e6; +} +.flatpickr-calendar.arrowBottom:after { + border-top-color: #fff; +} +.flatpickr-calendar:focus { + outline: 0; +} +.flatpickr-wrapper { + position: relative; + display: inline-block; +} +.flatpickr-months { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} +.flatpickr-months .flatpickr-month { + background: transparent; + color: rgba(0,0,0,0.9); + fill: rgba(0,0,0,0.9); + height: 28px; + line-height: 1; + text-align: center; + position: relative; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + overflow: hidden; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +.flatpickr-months .flatpickr-prev-month, +.flatpickr-months .flatpickr-next-month { + text-decoration: none; + cursor: pointer; + position: absolute; + top: 0px; + line-height: 16px; + height: 28px; + padding: 10px; + z-index: 3; +} +.flatpickr-months .flatpickr-prev-month.disabled, +.flatpickr-months .flatpickr-next-month.disabled { + display: none; +} +.flatpickr-months .flatpickr-prev-month i, +.flatpickr-months .flatpickr-next-month i { + position: relative; +} +.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month, +.flatpickr-months .flatpickr-next-month.flatpickr-prev-month { +/* + /*rtl:begin:ignore*/ +/* + */ + left: 0; +/* + /*rtl:end:ignore*/ +/* + */ +} +/* /*rtl:begin:ignore*/ /* /*rtl:end:ignore*/ -.flatpickr-months .flatpickr-prev-month.flatpickr-next-month,.flatpickr-months .flatpickr-next-month.flatpickr-next-month{/* - /*rtl:begin:ignore*/right:0;/* - /*rtl:end:ignore*/}/* +.flatpickr-months .flatpickr-prev-month.flatpickr-next-month, +.flatpickr-months .flatpickr-next-month.flatpickr-next-month { +/* + /*rtl:begin:ignore*/ +/* + */ + right: 0; +/* + /*rtl:end:ignore*/ +/* + */ +} +/* /*rtl:begin:ignore*/ /* /*rtl:end:ignore*/ -.flatpickr-months .flatpickr-prev-month:hover,.flatpickr-months .flatpickr-next-month:hover{color:#959ea9;}.flatpickr-months .flatpickr-prev-month:hover svg,.flatpickr-months .flatpickr-next-month:hover svg{fill:#f64747}.flatpickr-months .flatpickr-prev-month svg,.flatpickr-months .flatpickr-next-month svg{width:14px;height:14px;}.flatpickr-months .flatpickr-prev-month svg path,.flatpickr-months .flatpickr-next-month svg path{-webkit-transition:fill .1s;transition:fill .1s;fill:inherit}.numInputWrapper{position:relative;height:auto;}.numInputWrapper input,.numInputWrapper span{display:inline-block}.numInputWrapper input{width:100%;}.numInputWrapper input::-ms-clear{display:none}.numInputWrapper span{position:absolute;right:0;width:14px;padding:0 4px 0 2px;height:50%;line-height:50%;opacity:0;cursor:pointer;border:1px solid rgba(57,57,57,0.15);-webkit-box-sizing:border-box;box-sizing:border-box;}.numInputWrapper span:hover{background:rgba(0,0,0,0.1)}.numInputWrapper span:active{background:rgba(0,0,0,0.2)}.numInputWrapper span:after{display:block;content:"";position:absolute}.numInputWrapper span.arrowUp{top:0;border-bottom:0;}.numInputWrapper span.arrowUp:after{border-left:4px solid transparent;border-right:4px solid transparent;border-bottom:4px solid rgba(57,57,57,0.6);top:26%}.numInputWrapper span.arrowDown{top:50%;}.numInputWrapper span.arrowDown:after{border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid rgba(57,57,57,0.6);top:40%}.numInputWrapper span svg{width:inherit;height:auto;}.numInputWrapper span svg path{fill:rgba(0,0,0,0.5)}.numInputWrapper:hover{background:rgba(0,0,0,0.05);}.numInputWrapper:hover span{opacity:1}.flatpickr-current-month{font-size:135%;line-height:inherit;font-weight:300;color:inherit;position:absolute;width:75%;left:12.5%;padding:6.16px 0 0 0;line-height:1;height:28px;display:inline-block;text-align:center;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);}.flatpickr-current-month span.cur-month{font-family:inherit;font-weight:700;color:inherit;display:inline-block;margin-left:.5ch;padding:0;}.flatpickr-current-month span.cur-month:hover{background:rgba(0,0,0,0.05)}.flatpickr-current-month .numInputWrapper{width:6ch;width:7ch\0;display:inline-block;}.flatpickr-current-month .numInputWrapper span.arrowUp:after{border-bottom-color:rgba(0,0,0,0.9)}.flatpickr-current-month .numInputWrapper span.arrowDown:after{border-top-color:rgba(0,0,0,0.9)}.flatpickr-current-month input.cur-year{background:transparent;-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;cursor:text;padding:0 0 0 .5ch;margin:0;display:inline-block;font-size:inherit;font-family:inherit;font-weight:300;line-height:inherit;height:auto;border:0;border-radius:0;vertical-align:initial;}.flatpickr-current-month input.cur-year:focus{outline:0}.flatpickr-current-month input.cur-year[disabled],.flatpickr-current-month input.cur-year[disabled]:hover{font-size:100%;color:rgba(0,0,0,0.5);background:transparent;pointer-events:none}.flatpickr-weekdays{background:transparent;text-align:center;overflow:hidden;width:100%;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;height:28px;}.flatpickr-weekdays .flatpickr-weekdaycontainer{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}span.flatpickr-weekday{cursor:default;font-size:90%;background:transparent;color:rgba(0,0,0,0.54);line-height:1;margin:0;text-align:center;display:block;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;font-weight:bolder}.dayContainer,.flatpickr-weeks{padding:1px 0 0 0}.flatpickr-days{position:relative;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-align:start;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start;width:307.875px;}.flatpickr-days:focus{outline:0}.dayContainer{padding:0;outline:0;text-align:left;width:307.875px;min-width:307.875px;max-width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;display:inline-block;display:-ms-flexbox;display:-webkit-box;display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-wrap:wrap;-ms-flex-pack:justify;-webkit-justify-content:space-around;justify-content:space-around;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);opacity:1;}.dayContainer + .dayContainer{-webkit-box-shadow:-1px 0 0 #e6e6e6;box-shadow:-1px 0 0 #e6e6e6}.flatpickr-day{background:none;border:1px solid transparent;border-radius:150px;-webkit-box-sizing:border-box;box-sizing:border-box;color:#393939;cursor:pointer;font-weight:400;width:14.2857143%;-webkit-flex-basis:14.2857143%;-ms-flex-preferred-size:14.2857143%;flex-basis:14.2857143%;max-width:39px;height:39px;line-height:39px;margin:0;display:inline-block;position:relative;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;text-align:center;}.flatpickr-day.inRange,.flatpickr-day.prevMonthDay.inRange,.flatpickr-day.nextMonthDay.inRange,.flatpickr-day.today.inRange,.flatpickr-day.prevMonthDay.today.inRange,.flatpickr-day.nextMonthDay.today.inRange,.flatpickr-day:hover,.flatpickr-day.prevMonthDay:hover,.flatpickr-day.nextMonthDay:hover,.flatpickr-day:focus,.flatpickr-day.prevMonthDay:focus,.flatpickr-day.nextMonthDay:focus{cursor:pointer;outline:0;background:#e6e6e6;border-color:#e6e6e6}.flatpickr-day.today{border-color:#959ea9;}.flatpickr-day.today:hover,.flatpickr-day.today:focus{border-color:#959ea9;background:#959ea9;color:#fff}.flatpickr-day.selected,.flatpickr-day.startRange,.flatpickr-day.endRange,.flatpickr-day.selected.inRange,.flatpickr-day.startRange.inRange,.flatpickr-day.endRange.inRange,.flatpickr-day.selected:focus,.flatpickr-day.startRange:focus,.flatpickr-day.endRange:focus,.flatpickr-day.selected:hover,.flatpickr-day.startRange:hover,.flatpickr-day.endRange:hover,.flatpickr-day.selected.prevMonthDay,.flatpickr-day.startRange.prevMonthDay,.flatpickr-day.endRange.prevMonthDay,.flatpickr-day.selected.nextMonthDay,.flatpickr-day.startRange.nextMonthDay,.flatpickr-day.endRange.nextMonthDay{background:#569ff7;-webkit-box-shadow:none;box-shadow:none;color:#fff;border-color:#569ff7}.flatpickr-day.selected.startRange,.flatpickr-day.startRange.startRange,.flatpickr-day.endRange.startRange{border-radius:50px 0 0 50px}.flatpickr-day.selected.endRange,.flatpickr-day.startRange.endRange,.flatpickr-day.endRange.endRange{border-radius:0 50px 50px 0}.flatpickr-day.selected.startRange + .endRange,.flatpickr-day.startRange.startRange + .endRange,.flatpickr-day.endRange.startRange + .endRange{-webkit-box-shadow:-10px 0 0 #569ff7;box-shadow:-10px 0 0 #569ff7}.flatpickr-day.selected.startRange.endRange,.flatpickr-day.startRange.startRange.endRange,.flatpickr-day.endRange.startRange.endRange{border-radius:50px}.flatpickr-day.inRange{border-radius:0;-webkit-box-shadow:-5px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-5px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-day.disabled,.flatpickr-day.disabled:hover,.flatpickr-day.prevMonthDay,.flatpickr-day.nextMonthDay,.flatpickr-day.notAllowed,.flatpickr-day.notAllowed.prevMonthDay,.flatpickr-day.notAllowed.nextMonthDay{color:rgba(57,57,57,0.3);background:transparent;border-color:transparent;cursor:default}.flatpickr-day.disabled,.flatpickr-day.disabled:hover{cursor:not-allowed;color:rgba(57,57,57,0.1)}.flatpickr-day.week.selected{border-radius:0;-webkit-box-shadow:-5px 0 0 #569ff7,5px 0 0 #569ff7;box-shadow:-5px 0 0 #569ff7,5px 0 0 #569ff7}.flatpickr-day.hidden{visibility:hidden}.rangeMode .flatpickr-day{margin-top:1px}.flatpickr-weekwrapper{display:inline-block;float:left;}.flatpickr-weekwrapper .flatpickr-weeks{padding:0 12px;-webkit-box-shadow:1px 0 0 #e6e6e6;box-shadow:1px 0 0 #e6e6e6}.flatpickr-weekwrapper .flatpickr-weekday{float:none;width:100%;line-height:28px}.flatpickr-weekwrapper span.flatpickr-day,.flatpickr-weekwrapper span.flatpickr-day:hover{display:block;width:100%;max-width:none;color:rgba(57,57,57,0.3);background:transparent;cursor:default;border:none}.flatpickr-innerContainer{display:block;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;}.flatpickr-rContainer{display:inline-block;padding:0;-webkit-box-sizing:border-box;box-sizing:border-box}.flatpickr-time{text-align:center;outline:0;display:block;height:0;line-height:40px;max-height:40px;-webkit-box-sizing:border-box;box-sizing:border-box;overflow:hidden;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;}.flatpickr-time:after{content:"";display:table;clear:both}.flatpickr-time .numInputWrapper{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;width:40%;height:40px;float:left;}.flatpickr-time .numInputWrapper span.arrowUp:after{border-bottom-color:#393939}.flatpickr-time .numInputWrapper span.arrowDown:after{border-top-color:#393939}.flatpickr-time.hasSeconds .numInputWrapper{width:26%}.flatpickr-time.time24hr .numInputWrapper{width:49%}.flatpickr-time input{background:transparent;-webkit-box-shadow:none;box-shadow:none;border:0;border-radius:0;text-align:center;margin:0;padding:0;height:inherit;line-height:inherit;cursor:pointer;color:#393939;font-size:14px;position:relative;-webkit-box-sizing:border-box;box-sizing:border-box;}.flatpickr-time input.flatpickr-hour{font-weight:bold}.flatpickr-time input.flatpickr-minute,.flatpickr-time input.flatpickr-second{font-weight:400}.flatpickr-time input:focus{outline:0;border:0}.flatpickr-time .flatpickr-time-separator,.flatpickr-time .flatpickr-am-pm{height:inherit;display:inline-block;float:left;line-height:inherit;color:#393939;font-weight:bold;width:2%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.flatpickr-time .flatpickr-am-pm{outline:0;width:18%;cursor:pointer;text-align:center;font-weight:400;}.flatpickr-time .flatpickr-am-pm:hover,.flatpickr-time .flatpickr-am-pm:focus{background:#f0f0f0}.flatpickr-input[readonly]{cursor:pointer}@-webkit-keyframes fpFadeInDown{from{opacity:0;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:1;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}@keyframes fpFadeInDown{from{opacity:0;-webkit-transform:translate3d(0,-20px,0);transform:translate3d(0,-20px,0)}to{opacity:1;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}} +.flatpickr-months .flatpickr-prev-month:hover, +.flatpickr-months .flatpickr-next-month:hover { + color: #959ea9; +} +.flatpickr-months .flatpickr-prev-month:hover svg, +.flatpickr-months .flatpickr-next-month:hover svg { + fill: #f64747; +} +.flatpickr-months .flatpickr-prev-month svg, +.flatpickr-months .flatpickr-next-month svg { + width: 14px; + height: 14px; +} +.flatpickr-months .flatpickr-prev-month svg path, +.flatpickr-months .flatpickr-next-month svg path { + -webkit-transition: fill 0.1s; + transition: fill 0.1s; + fill: inherit; +} +.numInputWrapper { + position: relative; + height: auto; +} +.numInputWrapper input, +.numInputWrapper span { + display: inline-block; +} +.numInputWrapper input { + width: 100%; + min-width: auto !important; +} +.numInputWrapper input::-ms-clear { + display: none; +} +.numInputWrapper span { + position: absolute; + right: 0; + width: 14px; + padding: 0 4px 0 2px; + height: 50%; + line-height: 50%; + opacity: 0; + cursor: pointer; + border: 1px solid rgba(57,57,57,0.15); + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.numInputWrapper span:hover { + background: rgba(0,0,0,0.1); +} +.numInputWrapper span:active { + background: rgba(0,0,0,0.2); +} +.numInputWrapper span:after { + display: block; + content: ""; + position: absolute; +} +.numInputWrapper span.arrowUp { + top: 0; + border-bottom: 0; +} +.numInputWrapper span.arrowUp:after { + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-bottom: 4px solid rgba(57,57,57,0.6); + top: 26%; +} +.numInputWrapper span.arrowDown { + top: 50%; +} +.numInputWrapper span.arrowDown:after { + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid rgba(57,57,57,0.6); + top: 40%; +} +.numInputWrapper span svg { + width: inherit; + height: auto; +} +.numInputWrapper span svg path { + fill: rgba(0,0,0,0.5); +} +.numInputWrapper:hover { + background: rgba(0,0,0,0.05); +} +.numInputWrapper:hover span { + opacity: 1; +} +.flatpickr-current-month { + font-size: 135%; + line-height: inherit; + font-weight: 300; + color: inherit; + position: absolute; + width: 75%; + left: 12.5%; + padding: 6.16px 0 0 0; + line-height: 1; + height: 28px; + display: inline-block; + text-align: center; + -webkit-transform: translate3d(0px, 0px, 0px); + transform: translate3d(0px, 0px, 0px); +} +.flatpickr-current-month span.cur-month { + font-family: inherit; + font-weight: 700; + color: inherit; + display: inline-block; + margin-left: 0.5ch; + padding: 0; +} +.flatpickr-current-month span.cur-month:hover { + background: rgba(0,0,0,0.05); +} +.flatpickr-current-month .numInputWrapper { + width: 6ch; + width: 7ch\0; + display: inline-block; +} +.flatpickr-current-month .numInputWrapper span.arrowUp:after { + border-bottom-color: rgba(0,0,0,0.9); +} +.flatpickr-current-month .numInputWrapper span.arrowDown:after { + border-top-color: rgba(0,0,0,0.9); +} +.flatpickr-current-month input.cur-year { + background: transparent; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: inherit; + cursor: text; + padding: 0 0 0 0.5ch; + margin: 0; + display: inline-block; + font-size: inherit; + font-family: inherit; + font-weight: 300; + line-height: inherit; + height: auto; + border: 0; + border-radius: 0; + vertical-align: initial; +} +.flatpickr-current-month input.cur-year:focus { + outline: 0; +} +.flatpickr-current-month input.cur-year[disabled], +.flatpickr-current-month input.cur-year[disabled]:hover { + font-size: 100%; + color: rgba(0,0,0,0.5); + background: transparent; + pointer-events: none; +} +.flatpickr-weekdays { + background: transparent; + text-align: center; + overflow: hidden; + width: 100%; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + -ms-flex-align: center; + align-items: center; + height: 28px; +} +.flatpickr-weekdays .flatpickr-weekdaycontainer { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; +} +span.flatpickr-weekday { + cursor: default; + font-size: 90%; + background: transparent; + color: rgba(0,0,0,0.54); + line-height: 1; + margin: 0; + text-align: center; + display: block; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + font-weight: bolder; +} +.dayContainer, +.flatpickr-weeks { + padding: 1px 0 0 0; +} +.flatpickr-days { + position: relative; + overflow: hidden; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: start; + -webkit-align-items: flex-start; + -ms-flex-align: start; + align-items: flex-start; + width: 307.875px; +} +.flatpickr-days:focus { + outline: 0; +} +.dayContainer { + padding: 0; + outline: 0; + text-align: left; + width: 307.875px; + min-width: 307.875px; + max-width: 307.875px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + display: inline-block; + display: -ms-flexbox; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-wrap: wrap; + -ms-flex-pack: justify; + -webkit-justify-content: space-around; + justify-content: space-around; + -webkit-transform: translate3d(0px, 0px, 0px); + transform: translate3d(0px, 0px, 0px); + opacity: 1; +} +.dayContainer + .dayContainer { + -webkit-box-shadow: -1px 0 0 #e6e6e6; + box-shadow: -1px 0 0 #e6e6e6; +} +.flatpickr-day { + background: none; + border: 1px solid transparent; + border-radius: 150px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + color: #393939; + cursor: pointer; + font-weight: 400; + width: 14.2857143%; + -webkit-flex-basis: 14.2857143%; + -ms-flex-preferred-size: 14.2857143%; + flex-basis: 14.2857143%; + max-width: 39px; + height: 39px; + line-height: 39px; + margin: 0; + display: inline-block; + position: relative; + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + text-align: center; +} +.flatpickr-day.inRange, +.flatpickr-day.prevMonthDay.inRange, +.flatpickr-day.nextMonthDay.inRange, +.flatpickr-day.today.inRange, +.flatpickr-day.prevMonthDay.today.inRange, +.flatpickr-day.nextMonthDay.today.inRange, +.flatpickr-day:hover, +.flatpickr-day.prevMonthDay:hover, +.flatpickr-day.nextMonthDay:hover, +.flatpickr-day:focus, +.flatpickr-day.prevMonthDay:focus, +.flatpickr-day.nextMonthDay:focus { + cursor: pointer; + outline: 0; + background: #e6e6e6; + border-color: #e6e6e6; +} +.flatpickr-day.today { + border-color: #959ea9; +} +.flatpickr-day.today:hover, +.flatpickr-day.today:focus { + border-color: #959ea9; + background: #959ea9; + color: #fff; +} +.flatpickr-day.selected, +.flatpickr-day.startRange, +.flatpickr-day.endRange, +.flatpickr-day.selected.inRange, +.flatpickr-day.startRange.inRange, +.flatpickr-day.endRange.inRange, +.flatpickr-day.selected:focus, +.flatpickr-day.startRange:focus, +.flatpickr-day.endRange:focus, +.flatpickr-day.selected:hover, +.flatpickr-day.startRange:hover, +.flatpickr-day.endRange:hover, +.flatpickr-day.selected.prevMonthDay, +.flatpickr-day.startRange.prevMonthDay, +.flatpickr-day.endRange.prevMonthDay, +.flatpickr-day.selected.nextMonthDay, +.flatpickr-day.startRange.nextMonthDay, +.flatpickr-day.endRange.nextMonthDay { + background: #569ff7; + -webkit-box-shadow: none; + box-shadow: none; + color: #fff; + border-color: #569ff7; +} +.flatpickr-day.selected.startRange, +.flatpickr-day.startRange.startRange, +.flatpickr-day.endRange.startRange { + border-radius: 50px 0 0 50px; +} +.flatpickr-day.selected.endRange, +.flatpickr-day.startRange.endRange, +.flatpickr-day.endRange.endRange { + border-radius: 0 50px 50px 0; +} +.flatpickr-day.selected.startRange + .endRange, +.flatpickr-day.startRange.startRange + .endRange, +.flatpickr-day.endRange.startRange + .endRange { + -webkit-box-shadow: -10px 0 0 #569ff7; + box-shadow: -10px 0 0 #569ff7; +} +.flatpickr-day.selected.startRange.endRange, +.flatpickr-day.startRange.startRange.endRange, +.flatpickr-day.endRange.startRange.endRange { + border-radius: 50px; +} +.flatpickr-day.inRange { + border-radius: 0; + -webkit-box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; + box-shadow: -5px 0 0 #e6e6e6, 5px 0 0 #e6e6e6; +} +.flatpickr-day.disabled, +.flatpickr-day.disabled:hover, +.flatpickr-day.prevMonthDay, +.flatpickr-day.nextMonthDay, +.flatpickr-day.notAllowed, +.flatpickr-day.notAllowed.prevMonthDay, +.flatpickr-day.notAllowed.nextMonthDay { + color: rgba(57,57,57,0.3); + background: transparent; + border-color: transparent; + cursor: default; +} +.flatpickr-day.disabled, +.flatpickr-day.disabled:hover { + cursor: not-allowed; + color: rgba(57,57,57,0.1); +} +.flatpickr-day.week.selected { + border-radius: 0; + -webkit-box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7; + box-shadow: -5px 0 0 #569ff7, 5px 0 0 #569ff7; +} +.flatpickr-day.hidden { + visibility: hidden; +} +.rangeMode .flatpickr-day { + margin-top: 1px; +} +.flatpickr-weekwrapper { + display: inline-block; + float: left; +} +.flatpickr-weekwrapper .flatpickr-weeks { + padding: 0 12px; + -webkit-box-shadow: 1px 0 0 #e6e6e6; + box-shadow: 1px 0 0 #e6e6e6; +} +.flatpickr-weekwrapper .flatpickr-weekday { + float: none; + width: 100%; + line-height: 28px; +} +.flatpickr-weekwrapper span.flatpickr-day, +.flatpickr-weekwrapper span.flatpickr-day:hover { + display: block; + width: 100%; + max-width: none; + color: rgba(57,57,57,0.3); + background: transparent; + cursor: default; + border: none; +} +.flatpickr-innerContainer { + display: block; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-sizing: border-box; + box-sizing: border-box; + overflow: hidden; +} +.flatpickr-rContainer { + display: inline-block; + padding: 0; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.flatpickr-time { + text-align: center; + outline: 0; + display: block; + height: 0; + line-height: 40px; + max-height: 40px; + -webkit-box-sizing: border-box; + box-sizing: border-box; + overflow: hidden; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} +.flatpickr-time:after { + content: ""; + display: table; + clear: both; +} +.flatpickr-time .numInputWrapper { + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + width: 40%; + height: 40px; + float: left; +} +.flatpickr-time .numInputWrapper span.arrowUp:after { + border-bottom-color: #393939; +} +.flatpickr-time .numInputWrapper span.arrowDown:after { + border-top-color: #393939; +} +.flatpickr-time.hasSeconds .numInputWrapper { + width: 26%; +} +.flatpickr-time.time24hr .numInputWrapper { + width: 49%; +} +.flatpickr-time input { + background: transparent; + -webkit-box-shadow: none; + box-shadow: none; + border: 0; + border-radius: 0; + text-align: center; + margin: 0; + padding: 0; + height: inherit; + line-height: inherit; + cursor: pointer; + color: #393939; + font-size: 14px; + position: relative; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +.flatpickr-time input.flatpickr-hour { + font-weight: bold; +} +.flatpickr-time input.flatpickr-minute, +.flatpickr-time input.flatpickr-second { + font-weight: 400; +} +.flatpickr-time input:focus { + outline: 0; + border: 0; +} +.flatpickr-time .flatpickr-time-separator, +.flatpickr-time .flatpickr-am-pm { + height: inherit; + display: inline-block; + float: left; + line-height: inherit; + color: #393939; + font-weight: bold; + width: 2%; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-align-self: center; + -ms-flex-item-align: center; + align-self: center; +} +.flatpickr-time .flatpickr-am-pm { + outline: 0; + width: 18%; + cursor: pointer; + text-align: center; + font-weight: 400; +} +.flatpickr-time .flatpickr-am-pm:hover, +.flatpickr-time .flatpickr-am-pm:focus { + background: #f0f0f0; +} +.flatpickr-input[readonly] { + cursor: pointer; + min-width: auto; +} +@-webkit-keyframes fpFadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} +@keyframes fpFadeInDown { + from { + opacity: 0; + -webkit-transform: translate3d(0, -20px, 0); + transform: translate3d(0, -20px, 0); + } + to { + opacity: 1; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} diff --git a/templates/home.hamlet b/templates/home.hamlet index e5a4a8678..91a0b71cb 100644 --- a/templates/home.hamlet +++ b/templates/home.hamlet @@ -28,40 +28,12 @@
-

Tabellen - - - - - - - - - - - - - + -
ID - TH1 - TH2 - TH3 -
0 - NT2 - CON2 - 3 -
1 - 5 - ONT2 - 13 -
2 - CONT1 - NT2 - 43 -
3 - 43 - T2C2 - 35 -
4 - 73 - CA62 - 7 +

Date picker + + + + +
diff --git a/templates/home.julius b/templates/home.julius index e69de29bb..c62129e2c 100644 --- a/templates/home.julius +++ b/templates/home.julius @@ -0,0 +1,19 @@ +document.addEventListener('DOMContentLoaded', function() { + 'use strict'; + + var config = { + enableTime: true, + altInput: true, + altFormat: "j. F Y, H:i", + dateFormat: "Y-m-d H:i", + time_24hr: true + }; + + flatpickr('#datetime-form1 [type="date"]', { + altFormat: "j. F Y", dateFormat: "Y-m-d", altInput: true + }); + flatpickr('#datetime-form1 [type="time"]', { + enableTime: true, noCalendar: true, altFormat: "H:i", dateFormat: "H:i", altInput: true, time_24hr: true + }); + flatpickr('#datetime-form2 input', config); +}); From cdf2cec832239921b914d6aae95f348dd62b34ea Mon Sep 17 00:00:00 2001 From: SJost Date: Thu, 19 Apr 2018 09:29:16 +0200 Subject: [PATCH 008/559] minor comment added --- src/Handler/Submission.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index 304101890..9887d1f0c 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -58,7 +58,7 @@ makeSubmissionForm unpackZips grouping = identForm FIDsubmission $ \html -> do <* submitButton where groupNr - | Arbitrary{..} <- grouping = pred maxParticipants + | Arbitrary{..} <- grouping = pred maxParticipants -- pred to account for the person submitting | otherwise = 0 getSubmissionR, postSubmissionR :: TermId -> Text -> Text -> SubmissionMode -> Handler Html From d741dabc250ed04fb5161f8868f3c54f41a80377 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Thu, 19 Apr 2018 10:11:52 +0200 Subject: [PATCH 009/559] mforced & aforced --- src/Handler/Utils/Form.hs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index 1c5c94f1e..665acff68 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -40,6 +40,8 @@ import qualified Database.Esqueleto.Internal.Sql as E import qualified Data.Set as Set +import Control.Monad.Writer.Class + ------------------------------------------------ -- Unique Form Identifiers to avoid accidents -- ------------------------------------------------ @@ -426,3 +428,26 @@ optionsPersistCryptoId filts ords toDisplay = fmap mkOptionList $ do , optionInternalValue = key , optionExternalValue = toPathPiece (cId :: CryptoID UUID (Key a)) }) cPairs + +mforced :: (site ~ HandlerSite m, MonadHandler m) + => Field m a -> FieldSettings site -> a -> MForm m (FormResult a, FieldView site) +mforced Field{..} FieldSettings{..} val = do + tell fieldEnctype + name <- maybe newFormIdent return fsName + theId <- lift $ maybe newIdent return fsId + mr <- getMessageRender + let fsAttrs' = fsAttrs <> [("disabled", "")] + return ( FormSuccess val + , FieldView + { fvLabel = toHtml $ mr fsLabel + , fvTooltip = toHtml <$> fmap mr fsTooltip + , fvId = theId + , fvInput = fieldView theId name fsAttrs' (Right val) False + , fvErrors = Nothing + , fvRequired = False + } + ) + +aforced :: (RenderMessage site FormMessage, HandlerSite m ~ site, MonadHandler m) + => Field m a -> FieldSettings site -> a -> AForm m a +aforced field settings val = formToAForm $ second pure <$> mforced field settings val From efcbb82d25505f7e7ab1d9844155ced0a1d797c3 Mon Sep 17 00:00:00 2001 From: SJost Date: Thu, 19 Apr 2018 10:45:05 +0200 Subject: [PATCH 010/559] ExcerciseBuddies working --- src/Handler/Submission.hs | 46 +++++++++++++++++++++++++++++++-------- src/Handler/Utils/Form.hs | 1 - 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index 9887d1f0c..941b4e38b 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -1,6 +1,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ParallelListComp #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE MultiParamTypeClasses #-} @@ -50,24 +51,28 @@ import qualified Text.Blaze.Html5.Attributes as HA -makeSubmissionForm :: Bool -> SheetGroup -> Form (Source Handler File, [Text]) -makeSubmissionForm unpackZips grouping = identForm FIDsubmission $ \html -> do +makeSubmissionForm :: Bool -> SheetGroup -> [Text] -> Form (Source Handler File, [Text]) +makeSubmissionForm unpackZips grouping buddies = identForm FIDsubmission $ \html -> do flip (renderAForm FormStandard) html $ (,) <$> areq (zipFileField unpackZips) (fsm $ bool MsgSubmissionFile MsgSubmissionArchive unpackZips) Nothing - <*> (catMaybes <$> sequenceA [aopt textField (fsm $ MsgSubmissionMember g) Nothing | g <- [1..groupNr] ]) -- TODO: Convenience: preselect last buddies + <*> (catMaybes <$> sequenceA [aopt textField (fsm $ MsgSubmissionMember g) buddy + | g <- [1..(max groupNr $ length buddies)] -- groupNr might have decreased meanwhile + | buddy <- map (Just . Just) buddies ++ repeat Nothing -- show current buddies + ]) <* submitButton where groupNr | Arbitrary{..} <- grouping = pred maxParticipants -- pred to account for the person submitting | otherwise = 0 + getSubmissionR, postSubmissionR :: TermId -> Text -> Text -> SubmissionMode -> Handler Html getSubmissionR = postSubmissionR postSubmissionR tid csh shn (SubmissionMode mcid) = do uid <- requireAuthId msmid <- traverse decrypt mcid - (Entity shid Sheet{..}) <- runDB $ do - sheet@(Entity shid _) <- fetchSheet tid csh shn + (Entity shid Sheet{..}, buddies) <- runDB $ do + sheet@(Entity shid Sheet{..}) <- fetchSheet tid csh shn case msmid of Nothing -> do submissions <- E.select . E.from $ \(submission `E.InnerJoin` submissionUser) -> do @@ -77,18 +82,41 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do return $ submission E.^. SubmissionId $logDebugS "Submission.DUPLICATENEW" (tshow submissions) case submissions of - [] -> return shid + [] -> do + -- fetch buddies from previous submission in this course + buddies <- E.select . E.from $ \(submissionUser `E.InnerJoin` user) -> do + E.on (submissionUser E.^. SubmissionUserUserId E.==. user E.^. UserId) + let oldids = E.subList_select . E.from $ \(sheet `E.InnerJoin` submission `E.InnerJoin` submissionUser `E.InnerJoin` submissionEdit) -> do + E.on (submissionEdit E.^. SubmissionEditSubmission E.==. submission E.^. SubmissionId) + E.on (submissionUser E.^. SubmissionUserSubmissionId E.==. submission E.^. SubmissionId) + E.on (sheet E.^. SheetId E.==. submission E.^. SubmissionSheetId) + E.where_ $ submissionUser E.^. SubmissionUserUserId E.==. E.val uid + E.&&. sheet E.^. SheetCourseId E.==. E.val sheetCourseId + E.orderBy [E.desc $ submissionEdit E.^. SubmissionEditTime] + E.limit 1 + return $ submission E.^. SubmissionId + E.where_ $ submissionUser E.^. SubmissionUserSubmissionId `E.in_` oldids + E.&&. submissionUser E.^. SubmissionUserUserId E.!=. E.val uid + E.orderBy [E.asc $ user E.^. UserEmail] + return $ user E.^. UserEmail + return (sheet,buddies) (E.Value smid:_) -> do cID <- encrypt smid addMessageI "info" $ MsgSubmissionAlreadyExists redirect $ CourseR tid csh $ SheetR $ SubmissionR shn $ SubmissionMode $ Just cID - return sheet (Just smid) -> do shid' <- submissionSheetId <$> get404 smid when (shid /= shid') $ invalidArgsI [MsgSubmissionWrongSheet] - return sheet + -- fetch buddies from current submission + buddies <- E.select . E.from $ \(submissionUser `E.InnerJoin` user) -> do + E.on (submissionUser E.^. SubmissionUserUserId E.==. user E.^. UserId) + E.where_ $ submissionUser E.^. SubmissionUserSubmissionId E.==. E.val smid + E.&&. submissionUser E.^. SubmissionUserUserId E.!=. E.val uid + E.orderBy [E.asc $ user E.^. UserEmail] + return $ user E.^. UserEmail + return (sheet,buddies) let unpackZips = True -- undefined -- TODO - ((res,formWidget), formEnctype) <- runFormPost $ makeSubmissionForm unpackZips sheetGrouping + ((res,formWidget), formEnctype) <- runFormPost $ makeSubmissionForm unpackZips sheetGrouping $ map E.unValue buddies mCID <- runDB $ do res' <- case res of (FormMissing ) -> return $ FormMissing diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index 1c5c94f1e..1f954318c 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -232,7 +232,6 @@ posIntField d = checkBool (>= 1) (T.append d " muss eine positive Zahl sein.") minIntField :: (Monad m, Integral i, Show i, RenderMessage (HandlerSite m) FormMessage) => i -> Text -> Field m i minIntField m d = checkBool (>= m) (T.concat [d," muss größer als ", T.pack $ show m, " sein."]) $ intField - --termField: see Utils.Term schoolField :: Field Handler SchoolId From 11c17e3dc8c9768ad7df0c9b1ce543d84c5f9cea Mon Sep 17 00:00:00 2001 From: SJost Date: Thu, 19 Apr 2018 10:56:32 +0200 Subject: [PATCH 011/559] ExcerciseBuddies for DefinedGroups are forced --- src/Handler/Submission.hs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index 941b4e38b..7aadaa67c 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -55,15 +55,18 @@ makeSubmissionForm :: Bool -> SheetGroup -> [Text] -> Form (Source Handler File, makeSubmissionForm unpackZips grouping buddies = identForm FIDsubmission $ \html -> do flip (renderAForm FormStandard) html $ (,) <$> areq (zipFileField unpackZips) (fsm $ bool MsgSubmissionFile MsgSubmissionArchive unpackZips) Nothing - <*> (catMaybes <$> sequenceA [aopt textField (fsm $ MsgSubmissionMember g) buddy + <*> (catMaybes <$> sequenceA [bool aforced' aopt editableBuddies textField (fsm $ MsgSubmissionMember g) buddy | g <- [1..(max groupNr $ length buddies)] -- groupNr might have decreased meanwhile | buddy <- map (Just . Just) buddies ++ repeat Nothing -- show current buddies ]) <* submitButton where - groupNr - | Arbitrary{..} <- grouping = pred maxParticipants -- pred to account for the person submitting - | otherwise = 0 + (groupNr, editableBuddies) + | Arbitrary{..} <- grouping = (pred maxParticipants, True) -- pred to account for the person submitting + | otherwise = (0, False) + + aforced' f fs (Just (Just v)) = Just <$> aforced f fs v + aforced' _ _ _ = error "Cannot happen since groupNr==0 if grouping/=Arbitrary" getSubmissionR, postSubmissionR :: TermId -> Text -> Text -> SubmissionMode -> Handler Html From 7329c84304cdb74b43b57e6b0899bd84c20a85c1 Mon Sep 17 00:00:00 2001 From: SJost Date: Thu, 19 Apr 2018 11:22:35 +0200 Subject: [PATCH 012/559] SubmissionEdit page offers downloads now. --- src/Handler/Submission.hs | 41 ++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index 7aadaa67c..6e1158686 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -57,7 +57,7 @@ makeSubmissionForm unpackZips grouping buddies = identForm FIDsubmission $ \html <$> areq (zipFileField unpackZips) (fsm $ bool MsgSubmissionFile MsgSubmissionArchive unpackZips) Nothing <*> (catMaybes <$> sequenceA [bool aforced' aopt editableBuddies textField (fsm $ MsgSubmissionMember g) buddy | g <- [1..(max groupNr $ length buddies)] -- groupNr might have decreased meanwhile - | buddy <- map (Just . Just) buddies ++ repeat Nothing -- show current buddies + | buddy <- map (Just . Just) buddies ++ repeat Nothing -- show current buddies ]) <* submitButton where @@ -74,7 +74,7 @@ getSubmissionR = postSubmissionR postSubmissionR tid csh shn (SubmissionMode mcid) = do uid <- requireAuthId msmid <- traverse decrypt mcid - (Entity shid Sheet{..}, buddies) <- runDB $ do + (Entity shid Sheet{..}, buddies, oldfiles) <- runDB $ do sheet@(Entity shid Sheet{..}) <- fetchSheet tid csh shn case msmid of Nothing -> do @@ -102,7 +102,7 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do E.&&. submissionUser E.^. SubmissionUserUserId E.!=. E.val uid E.orderBy [E.asc $ user E.^. UserEmail] return $ user E.^. UserEmail - return (sheet,buddies) + return (sheet,buddies,[]) (E.Value smid:_) -> do cID <- encrypt smid addMessageI "info" $ MsgSubmissionAlreadyExists @@ -117,7 +117,8 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do E.&&. submissionUser E.^. SubmissionUserUserId E.!=. E.val uid E.orderBy [E.asc $ user E.^. UserEmail] return $ user E.^. UserEmail - return (sheet,buddies) + oldfiles <- sourceToList $ submissionFileSource smid + return (sheet,buddies,oldfiles) let unpackZips = True -- undefined -- TODO ((res,formWidget), formEnctype) <- runFormPost $ makeSubmissionForm unpackZips sheetGrouping $ map E.unValue buddies mCID <- runDB $ do @@ -189,6 +190,9 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do Just cID -> redirect $ CourseR tid csh $ SheetR $ SubmissionR shn $ SubmissionMode $ Just cID Nothing -> return () + mArCid <- fmap (CI.original . ciphertext) <$> traverse encrypt msmid + let mCidArCid = (,) <$> mcid <*> mArCid + let pageTitle = MsgSubmissionTitle (unTermKey tid) csh shn let formTitle = pageTitle let formText = Nothing :: Maybe UniWorXMessage @@ -196,12 +200,34 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do defaultLayout $ do setTitleI pageTitle $(widgetFile "formPageI18n") + [whamlet| + $maybe (cid, arCid) <-mCidArCid +
+

+ Archive +

Enthaltene Dateien: + $forall (Entity _ File{..}) <- oldfiles + + #{fileTitle} + |] +submissionFileSource :: SubmissionId -> Source (YesodDB UniWorX) (Entity File) +submissionFileSource submissionID = E.selectSource . E.from $ \(sf `E.InnerJoin` f) -> E.distinctOnOrderBy [E.asc $ f E.^. FileTitle] $ do + E.on (f E.^. FileId E.==. sf E.^. SubmissionFileFileId) + E.where_ $ sf E.^. SubmissionFileSubmissionId E.==. E.val submissionID + E.where_ . E.not_ $ sf E.^. SubmissionFileIsDeletion + E.orderBy [E.desc $ sf E.^. SubmissionFileIsUpdate] + return f + + + + + @@ -309,13 +335,6 @@ getSubmissionDownloadSingleR cID path = do [Entity _ File{ fileContent = Just c }] -> return $ TypedContent (defaultMimeLookup fileName) (toContent c) _ -> notFound -submissionFileSource :: SubmissionId -> Source (YesodDB UniWorX) (Entity File) -submissionFileSource submissionID = E.selectSource . E.from $ \(sf `E.InnerJoin` f) -> E.distinctOnOrderBy [E.asc $ f E.^. FileTitle] $ do - E.on (f E.^. FileId E.==. sf E.^. SubmissionFileFileId) - E.where_ $ sf E.^. SubmissionFileSubmissionId E.==. E.val submissionID - E.where_ . E.not_ $ sf E.^. SubmissionFileIsDeletion - E.orderBy [E.desc $ sf E.^. SubmissionFileIsUpdate] - return f postSubmissionDownloadMultiArchiveR :: Handler TypedContent postSubmissionDownloadMultiArchiveR = do From 3bd9a2a48396174288d1c36820604f5f72fb8ffe Mon Sep 17 00:00:00 2001 From: SJost Date: Thu, 19 Apr 2018 11:27:10 +0200 Subject: [PATCH 013/559] minor --- src/Handler/Submission.hs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index 6e1158686..9550a6791 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -204,11 +204,14 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do $maybe (cid, arCid) <-mCidArCid

- Archive + Archiv

Enthaltene Dateien: $forall (Entity _ File{..}) <- oldfiles #{fileTitle} +

+ Last Edits + TODO |] From 917d767d30a79a9d7d424053395343b0b28f12d8 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Thu, 19 Apr 2018 13:31:47 +0200 Subject: [PATCH 014/559] Cleanup SubmissionDownloadArchive --- routes | 7 ++-- src/CryptoID.hs | 13 ++++++ src/Foundation.hs | 2 +- src/Handler/Submission.hs | 82 ++++++++++++++++++------------------- templates/submission.hamlet | 2 +- 5 files changed, 57 insertions(+), 49 deletions(-) diff --git a/routes b/routes index 9552688ea..f311fb480 100644 --- a/routes +++ b/routes @@ -29,16 +29,15 @@ !/#Text/submission/#SubmissionMode SubmissionR GET POST !time +!/#{ZIPArchiveName SubmissionId} SubmissionDownloadArchiveR GET +!/#CryptoUUIDSubmission/#FilePath SubmissionDownloadSingleR GET +!/#UUID CryptoUUIDDispatchR GET -- TODO below /submission SubmissionListR GET POST /submission/#CryptoUUIDSubmission SubmissionDemoR GET POST /submissions.zip SubmissionDownloadMultiArchiveR POST -!/submission/archive/#FilePath SubmissionDownloadArchiveR GET -!/submission/#CryptoUUIDSubmission/#FilePath SubmissionDownloadSingleR GET - -!/#UUID CryptoUUIDDispatchR GET -- For demonstration /course/#CryptoUUIDCourse/edit CourseEditIDR GET diff --git a/src/CryptoID.hs b/src/CryptoID.hs index 7c04d7b3f..c66005307 100644 --- a/src/CryptoID.hs +++ b/src/CryptoID.hs @@ -4,6 +4,7 @@ {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-} +{-# LANGUAGE RecordWildCards, ViewPatterns, PatternGuards #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module CryptoID @@ -25,6 +26,9 @@ import System.FilePath.Cryptographic.ImplicitNamespace import Data.UUID.Types import Web.PathPieces +import Data.CaseInsensitive (CI) +import qualified Data.CaseInsensitive as CI + instance PathPiece UUID where fromPathPiece = fromString . unpack @@ -51,3 +55,12 @@ instance PathPiece SubmissionMode where toPathPiece (SubmissionMode Nothing) = "new" toPathPiece (SubmissionMode (Just x)) = toPathPiece x +newtype ZIPArchiveName objID = ZIPArchiveName (CryptoID (CI FilePath) objID) + deriving (Show, Read, Eq) + +instance PathPiece (ZIPArchiveName objID) where + fromPathPiece (map CI.mk . unpack -> s) + | Just s' <- stripSuffix (map CI.mk ".zip") s = Just . ZIPArchiveName . CryptoID . CI.mk $ map CI.original s' + | otherwise = Nothing + + toPathPiece (ZIPArchiveName CryptoID{..}) = pack (CI.foldedCase ciphertext) <> ".zip" diff --git a/src/Foundation.hs b/src/Foundation.hs index d02a5e08c..8f547d1db 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -251,7 +251,7 @@ isAuthorizedDB route@(routeAttrs -> attrs) writeable isAuthorizedDB UsersR _ = adminAccess Nothing isAuthorizedDB (SubmissionDemoR cID) _ = return Authorized -- submissionAccess $ Right cID isAuthorizedDB (SubmissionDownloadSingleR cID _) _ = submissionAccess $ Right cID -isAuthorizedDB (SubmissionDownloadArchiveR (splitExtension -> (baseName, _))) _ = submissionAccess . Left . CryptoID $ CI.mk baseName +isAuthorizedDB (SubmissionDownloadArchiveR (ZIPArchiveName cID)) _ = submissionAccess $ Left cID isAuthorizedDB TermEditR _ = adminAccess Nothing isAuthorizedDB (TermEditExistR _) _ = adminAccess Nothing isAuthorizedDB CourseNewR _ = lecturerAccess Nothing diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index 9550a6791..f101588f8 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -190,7 +190,7 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do Just cID -> redirect $ CourseR tid csh $ SheetR $ SubmissionR shn $ SubmissionMode $ Just cID Nothing -> return () - mArCid <- fmap (CI.original . ciphertext) <$> traverse encrypt msmid + mArCid <- fmap ZIPArchiveName <$> traverse encrypt msmid let mCidArCid = (,) <$> mcid <*> mArCid let pageTitle = MsgSubmissionTitle (unTermKey tid) csh shn @@ -227,7 +227,45 @@ submissionFileSource submissionID = E.selectSource . E.from $ \(sf `E.InnerJoin` E.orderBy [E.desc $ sf E.^. SubmissionFileIsUpdate] return f +getSubmissionDownloadSingleR :: CryptoUUIDSubmission -> FilePath -> Handler TypedContent +getSubmissionDownloadSingleR cID path = do + submissionID <- decrypt cID + cID' <- encrypt submissionID + runDB $ do + isRating <- maybe False (== submissionID) <$> isRatingFile path + case isRating of + True -> do + file <- runMaybeT $ lift . ratingFile cID' =<< MaybeT (getRating submissionID) + maybe notFound (return . toTypedContent . Text.decodeUtf8) $ fileContent =<< file + False -> do + results <- E.select . E.from $ \(sf `E.InnerJoin` f) -> E.distinctOnOrderBy [E.asc $ f E.^. FileTitle] $ do + E.on (f E.^. FileId E.==. sf E.^. SubmissionFileFileId) + E.where_ (sf E.^. SubmissionFileSubmissionId E.==. E.val submissionID) + E.where_ (f E.^. FileTitle E.==. E.val path) + E.where_ . E.not_ . E.isNothing $ f E.^. FileContent + E.where_ . E.not_ $ sf E.^. SubmissionFileIsDeletion + E.orderBy [E.desc $ sf E.^. SubmissionFileIsUpdate] + return f + + let fileName = Text.pack $ takeFileName path + case results of + [Entity _ File{ fileContent = Just c }] -> return $ TypedContent (defaultMimeLookup fileName) (toContent c) + _ -> notFound + +getSubmissionDownloadArchiveR :: ZIPArchiveName SubmissionId -> Handler TypedContent +getSubmissionDownloadArchiveR (ZIPArchiveName cID) = do + submissionID <- decrypt cID + cUUID <- encrypt submissionID + respondSourceDB "application/zip" $ do + rating <- lift $ getRating submissionID + case rating of + Nothing -> lift notFound + Just rating' -> do + let fileEntitySource' :: Source (YesodDB UniWorX) File + fileEntitySource' = submissionFileSource submissionID =$= Conduit.map entityVal >> yieldM (ratingFile cID rating') + info = ZipInfo { zipComment = Text.encodeUtf8 . tshow $ ciphertext (cUUID :: CryptoUUIDSubmission) } + fileEntitySource' =$= produceZip info =$= Conduit.map toFlushBuilder @@ -312,31 +350,6 @@ postSubmissionListR = do defaultLayout $(widgetFile "submission-list") -getSubmissionDownloadSingleR :: CryptoUUIDSubmission -> FilePath -> Handler TypedContent -getSubmissionDownloadSingleR cID path = do - submissionID <- decrypt cID - cID' <- encrypt submissionID - - runDB $ do - isRating <- maybe False (== submissionID) <$> isRatingFile path - case isRating of - True -> do - file <- runMaybeT $ lift . ratingFile cID' =<< MaybeT (getRating submissionID) - maybe notFound (return . toTypedContent . Text.decodeUtf8) $ fileContent =<< file - False -> do - results <- E.select . E.from $ \(sf `E.InnerJoin` f) -> E.distinctOnOrderBy [E.asc $ f E.^. FileTitle] $ do - E.on (f E.^. FileId E.==. sf E.^. SubmissionFileFileId) - E.where_ (sf E.^. SubmissionFileSubmissionId E.==. E.val submissionID) - E.where_ (f E.^. FileTitle E.==. E.val path) - E.where_ . E.not_ . E.isNothing $ f E.^. FileContent - E.where_ . E.not_ $ sf E.^. SubmissionFileIsDeletion - E.orderBy [E.desc $ sf E.^. SubmissionFileIsUpdate] - return f - - let fileName = Text.pack $ takeFileName path - case results of - [Entity _ File{ fileContent = Just c }] -> return $ TypedContent (defaultMimeLookup fileName) (toContent c) - _ -> notFound postSubmissionDownloadMultiArchiveR :: Handler TypedContent @@ -383,23 +396,6 @@ postSubmissionDownloadMultiArchiveR = do mapM_ fileEntitySource' ratedSubmissions =$= produceZip def =$= Conduit.map toFlushBuilder -getSubmissionDownloadArchiveR :: FilePath -> Handler TypedContent -getSubmissionDownloadArchiveR path = do - let (baseName, ext) = splitExtension path - cID :: CryptoFileNameSubmission - cID = CryptoID $ CI.mk baseName - unless (ext == ".zip") notFound - submissionID <- decrypt cID - cUUID <- encrypt submissionID - respondSourceDB "application/zip" $ do - rating <- lift $ getRating submissionID - case rating of - Nothing -> lift notFound - Just rating' -> do - let fileEntitySource' :: Source (YesodDB UniWorX) File - fileEntitySource' = submissionFileSource submissionID =$= Conduit.map entityVal >> yieldM (ratingFile cID rating') - info = ZipInfo { zipComment = Text.encodeUtf8 . tshow $ ciphertext (cUUID :: CryptoUUIDSubmission) } - fileEntitySource' =$= produceZip info =$= Conduit.map toFlushBuilder diff --git a/templates/submission.hamlet b/templates/submission.hamlet index d8ea8ae89..469e076bb 100644 --- a/templates/submission.hamlet +++ b/templates/submission.hamlet @@ -27,7 +27,7 @@
Abgabe herunterladen
- + $#
From f3aa2b32c95f7f5cd68afeaa516e3662cec2082d Mon Sep 17 00:00:00 2001 From: SJost Date: Thu, 19 Apr 2018 17:43:42 +0200 Subject: [PATCH 015/559] Submission edit times are shown now. --- messages/de.msg | 2 +- src/Handler/Submission.hs | 33 ++++++++++++++++++++------------- src/Utils.hs | 6 ++++++ 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/messages/de.msg b/messages/de.msg index 0ec55519f..d296157b4 100644 --- a/messages/de.msg +++ b/messages/de.msg @@ -30,7 +30,7 @@ OnlyUploadOneFile: Bitte nur eine Datei hochladen. SubmissionWrongSheet: Abgabenummer gehört nicht zum angegebenen Übungsblatt. SubmissionAlreadyExists: Sie haben bereits eine Abgabe zu diesem Übungsblatt. -SubmissionTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: #{termToText tid}-#{courseShortHand} #{sheetName}: Abgabe +SubmissionTitle tid@TermIdentifier courseShortHand@Text sheetName@Text: #{termToText tid}-#{courseShortHand} #{sheetName}: Abgabe editieren/anlegen SubmissionMember g@Int: Mitabgebende(r) ##{tshow g} SubmissionArchive: Zip-Archiv der Abgabedatei(en) SubmissionFile: Datei zur Abgabe diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index f101588f8..318551f4b 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -74,7 +74,7 @@ getSubmissionR = postSubmissionR postSubmissionR tid csh shn (SubmissionMode mcid) = do uid <- requireAuthId msmid <- traverse decrypt mcid - (Entity shid Sheet{..}, buddies, oldfiles) <- runDB $ do + (Entity shid Sheet{..}, buddies, oldfiles,lastEdits) <- runDB $ do sheet@(Entity shid Sheet{..}) <- fetchSheet tid csh shn case msmid of Nothing -> do @@ -83,7 +83,7 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do E.where_ $ submissionUser E.^. SubmissionUserUserId E.==. E.val uid E.&&. submission E.^. SubmissionSheetId E.==. E.val shid return $ submission E.^. SubmissionId - $logDebugS "Submission.DUPLICATENEW" (tshow submissions) + -- $logDebugS "Submission.DUPLICATENEW" (tshow submissions) case submissions of [] -> do -- fetch buddies from previous submission in this course @@ -102,7 +102,7 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do E.&&. submissionUser E.^. SubmissionUserUserId E.!=. E.val uid E.orderBy [E.asc $ user E.^. UserEmail] return $ user E.^. UserEmail - return (sheet,buddies,[]) + return (sheet,buddies,[],[]) (E.Value smid:_) -> do cID <- encrypt smid addMessageI "info" $ MsgSubmissionAlreadyExists @@ -118,7 +118,15 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do E.orderBy [E.asc $ user E.^. UserEmail] return $ user E.^. UserEmail oldfiles <- sourceToList $ submissionFileSource smid - return (sheet,buddies,oldfiles) + -- mLastEdit <- selectFirst [SubmissionEditSubmission ==. smid] [Desc SubmissionEditTime] + lastEditValues <- E.select . E.from $ \(user `E.InnerJoin` submissionEdit) -> do + E.on (user E.^. UserId E.==. submissionEdit E.^. SubmissionEditUser) + E.where_ $ submissionEdit E.^. SubmissionEditSubmission E.==. E.val smid + E.orderBy [E.desc $ submissionEdit E.^. SubmissionEditTime] + E.limit 1 + return $ (user E.^. UserDisplayName, submissionEdit E.^. SubmissionEditTime) + let lastEdits = map (\(a,b)-> (E.unValue a, E.unValue b)) lastEditValues + return (sheet,buddies,oldfiles,lastEdits) let unpackZips = True -- undefined -- TODO ((res,formWidget), formEnctype) <- runFormPost $ makeSubmissionForm unpackZips sheetGrouping $ map E.unValue buddies mCID <- runDB $ do @@ -191,7 +199,6 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do Nothing -> return () mArCid <- fmap ZIPArchiveName <$> traverse encrypt msmid - let mCidArCid = (,) <$> mcid <*> mArCid let pageTitle = MsgSubmissionTitle (unTermKey tid) csh shn let formTitle = pageTitle @@ -201,17 +208,17 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do setTitleI pageTitle $(widgetFile "formPageI18n") [whamlet| - $maybe (cid, arCid) <-mCidArCid + $maybe arCid <- mArCid

Archiv -

Enthaltene Dateien: - $forall (Entity _ File{..}) <- oldfiles - - #{fileTitle} -

- Last Edits - TODO + $maybe cid <- mcid +

Enthaltene Dateien: + $forall (Entity _ File{..}) <- oldfiles + + #{fileTitle} + $forall (name,time) <- lastEdits +
last edited by #{name} at #{formatTimeGerWDT time} |] diff --git a/src/Utils.hs b/src/Utils.hs index a1816cb71..25fbe61bb 100644 --- a/src/Utils.hs +++ b/src/Utils.hs @@ -49,6 +49,12 @@ whenIsJust :: Monad m => Maybe a -> (a -> m ()) -> m () whenIsJust (Just x) f = f x whenIsJust Nothing _ = return () +------------ +-- Tuples -- +------------ + +both :: (a -> b) -> (a,a) -> (b,b) +both f (x,y) = (f x, f y) ---------- From 56476ccb3e4cb9b603381a1c82183250226292e0 Mon Sep 17 00:00:00 2001 From: SJost Date: Thu, 26 Apr 2018 09:41:57 +0200 Subject: [PATCH 016/559] Uniwory und PageAction ExerciseSheet --- routes | 1 + src/Foundation.hs | 2 +- src/Handler/Home.hs | 2 +- src/Handler/Sheet.hs | 10 +++++++++- src/Handler/Submission.hs | 3 ++- src/Handler/Utils/Form.hs | 23 ++++++++++++++++++++++- src/Utils.hs | 4 ---- templates/home.hamlet | 2 +- 8 files changed, 37 insertions(+), 10 deletions(-) diff --git a/routes b/routes index f311fb480..c04ca7ada 100644 --- a/routes +++ b/routes @@ -12,6 +12,7 @@ /term/edit TermEditR GET POST !adminAny /term/#TermId/edit TermEditExistR GET !adminAny +-- For Pattern Synonyms see Foundation /course/ CourseListR GET !/course/new CourseNewR GET POST !lecturerAny !/course/#TermId CourseListTermR GET diff --git a/src/Foundation.hs b/src/Foundation.hs index 8f547d1db..ac6f25b6c 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -350,7 +350,7 @@ instance YesodBreadcrumbs UniWorX where breadcrumb SubmissionListR = return ("Abgaben", Just HomeR) - breadcrumb HomeR = return ("UniworkY", Nothing) + breadcrumb HomeR = return ("Uniworky", Nothing) breadcrumb (AuthR _) = return ("Login", Just HomeR) breadcrumb ProfileR = return ("Profile", Just HomeR) breadcrumb _ = return ("home", Nothing) diff --git a/src/Handler/Home.hs b/src/Handler/Home.hs index bce3b39da..9a08c934b 100644 --- a/src/Handler/Home.hs +++ b/src/Handler/Home.hs @@ -43,7 +43,7 @@ getHomeR :: Handler Html getHomeR = do (btnWdgt, btnEnctype) <- generateFormPost (buttonForm :: Form CreateButton) defaultLayout $ do - setTitle "Willkommen zum UniworkY Test!" + setTitle "Willkommen zum Uniworky Test!" $(widgetFile "home") diff --git a/src/Handler/Sheet.hs b/src/Handler/Sheet.hs index 3baa7ece0..d39e44dca 100644 --- a/src/Handler/Sheet.hs +++ b/src/Handler/Sheet.hs @@ -196,7 +196,15 @@ getSheetShowR tid csh shn = do return $ (file E.^. FileTitle, sheetFile E.^. SheetFileType) let fileLinks = map (\(E.Value fName, E.Value fType) -> CSheetR tid csh (SheetFileR shn fType fName)) fileNameTypes - defaultLayout $ do + let pageActions = + [ PageActionPrime $ MenuItem + { menuItemLabel = "Abgabe" + , menuItemIcon = Nothing + , menuItemRoute = CSheetR tid csh SheetNewR + , menuItemAccessCallback' = return True + } + ] + defaultLinkLayout pageActions $ do setTitle $ toHtml $ T.append "Übung " $ sheetName sheet $(widgetFile "sheetShow") [whamlet| Under Construction !!! |] -- TODO diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index 318551f4b..68c908895 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -42,6 +42,7 @@ import Data.Set (Set) import qualified Data.Set as Set import Data.Map (Map) import qualified Data.Map as Map +import Data.Bifunctor import System.FilePath @@ -125,7 +126,7 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do E.orderBy [E.desc $ submissionEdit E.^. SubmissionEditTime] E.limit 1 return $ (user E.^. UserDisplayName, submissionEdit E.^. SubmissionEditTime) - let lastEdits = map (\(a,b)-> (E.unValue a, E.unValue b)) lastEditValues + let lastEdits = map (bimap E.unValue E.unValue) lastEditValues return (sheet,buddies,oldfiles,lastEdits) let unpackZips = True -- undefined -- TODO ((res,formWidget), formEnctype) <- runFormPost $ makeSubmissionForm unpackZips sheetGrouping $ map E.unValue buddies diff --git a/src/Handler/Utils/Form.hs b/src/Handler/Utils/Form.hs index 9e20dcc92..b01b8376a 100644 --- a/src/Handler/Utils/Form.hs +++ b/src/Handler/Utils/Form.hs @@ -15,9 +15,11 @@ module Handler.Utils.Form where import Handler.Utils.Form.Types + +import Handler.Utils.DateTime + import Import import qualified Data.Char as Char -import Handler.Utils.DateTime import Data.String (IsString(..)) import qualified Data.Foldable as Foldable @@ -328,6 +330,25 @@ sheetGroupAFormReq d _other = -- TODO -- TODO, offer options to choose between Arbitrary/Registered/NoGroups Arbitrary <$> areq (natField "Abgabegruppengröße") d (Just 1) +{- +dayTimeField :: FieldSettings UniWorX -> Maybe UTCTime -> Form Handler UTCTime +dayTimeField fs mutc = do + let (mbDay,mbTime) = case mutcs of + Nothing -> return (Nothing,Nothing) + (Just utc) -> + + (dayResult, dayView) <- mreq dayField fs + + (result, view) <- (,) <$> dayField <*> timeField + where + (mbDay,mbTime) + | (Just utc) <- mutc = + let lt = utcToLocalTime ??? utcs + in (Just $ localDay lt, Just $ localTimeOfDay lt) + | otherwise = (Nothing,Nothing) +-} + + utcTimeField :: (Monad m, RenderMessage (HandlerSite m) FormMessage) => Field m UTCTime -- StackOverflow: dayToUTC <$> (areq (jqueryDayField def {...}) settings Nothing) -- TODO: Verify whether this is UTC or local time from Browser diff --git a/src/Utils.hs b/src/Utils.hs index 25fbe61bb..e1aebc0b6 100644 --- a/src/Utils.hs +++ b/src/Utils.hs @@ -53,10 +53,6 @@ whenIsJust Nothing _ = return () -- Tuples -- ------------ -both :: (a -> b) -> (a,a) -> (b,b) -both f (x,y) = (f x, f y) - - ---------- -- Maps -- ---------- diff --git a/templates/home.hamlet b/templates/home.hamlet index 77d84ad80..23af41335 100644 --- a/templates/home.hamlet +++ b/templates/home.hamlet @@ -1,5 +1,5 @@
-

UniworkY - Demo +

Uniworky - Demo

Testumgebung für die Re-Implementierung von UniWorX

From 0e8ed257f873f6c5776c26f8a9f9b7d388861b32 Mon Sep 17 00:00:00 2001 From: SJost Date: Thu, 26 Apr 2018 09:59:47 +0200 Subject: [PATCH 017/559] PageAction: newSubmission working --- src/CryptoID.hs | 3 +++ src/Handler/Sheet.hs | 2 +- src/Handler/Submission.hs | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/CryptoID.hs b/src/CryptoID.hs index c66005307..3720c461b 100644 --- a/src/CryptoID.hs +++ b/src/CryptoID.hs @@ -48,6 +48,9 @@ decCryptoIDs [ ''SubmissionId newtype SubmissionMode = SubmissionMode (Maybe CryptoUUIDSubmission) deriving (Show, Read, Eq) +newSubmission :: SubmissionMode +newSubmission = SubmissionMode Nothing + instance PathPiece SubmissionMode where fromPathPiece "new" = Just $ SubmissionMode Nothing fromPathPiece s = SubmissionMode . Just <$> fromPathPiece s diff --git a/src/Handler/Sheet.hs b/src/Handler/Sheet.hs index d39e44dca..455662401 100644 --- a/src/Handler/Sheet.hs +++ b/src/Handler/Sheet.hs @@ -200,7 +200,7 @@ getSheetShowR tid csh shn = do [ PageActionPrime $ MenuItem { menuItemLabel = "Abgabe" , menuItemIcon = Nothing - , menuItemRoute = CSheetR tid csh SheetNewR + , menuItemRoute = CSheetR tid csh (SubmissionR shn newSubmission) , menuItemAccessCallback' = return True } ] diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index 68c908895..ac25a999f 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -213,13 +213,13 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do


Archiv + $forall (name,time) <- lastEdits +
last edited by #{name} at #{formatTimeGerWDT time} $maybe cid <- mcid

Enthaltene Dateien: $forall (Entity _ File{..}) <- oldfiles #{fileTitle} - $forall (name,time) <- lastEdits -
last edited by #{name} at #{formatTimeGerWDT time} |] From e98cf70400a6afd3677e7125919cff13b4edaaa0 Mon Sep 17 00:00:00 2001 From: SJost Date: Thu, 26 Apr 2018 11:29:42 +0200 Subject: [PATCH 018/559] MODEL REFACTOR: dropped ID suffixes throughout --- fill-db.hs | 24 +++---- models | 111 +++++++++++++++--------------- src/Foundation.hs | 7 +- src/Handler/Course.hs | 28 ++++---- src/Handler/CryptoIDDispatch.hs | 6 +- src/Handler/Sheet.hs | 34 ++++----- src/Handler/Submission.hs | 70 +++++++++---------- src/Handler/Term.hs | 2 +- src/Handler/Utils/Rating.hs | 4 +- src/Handler/Utils/Sheet.hs | 6 +- src/Handler/Utils/Submission.hs | 24 +++---- templates/widgets/asidenav.hamlet | 2 +- 12 files changed, 161 insertions(+), 157 deletions(-) diff --git a/fill-db.hs b/fill-db.hs index 205d4afd5..4802b75e1 100755 --- a/fill-db.hs +++ b/fill-db.hs @@ -89,8 +89,8 @@ main = db $ do , courseDescription = Nothing , courseLinkExternal = Nothing , courseShorthand = "ffp" - , courseTermId = TermKey summer2018 - , courseSchoolId = ifi + , courseTerm = TermKey summer2018 + , courseSchool = ifi , courseCapacity = Just 20 , courseHasRegistration = True , courseRegisterFrom = Just now @@ -117,8 +117,8 @@ main = db $ do , courseDescription = Nothing , courseLinkExternal = Nothing , courseShorthand = "eip" - , courseTermId = TermKey summer2017 - , courseSchoolId = ifi + , courseTerm = TermKey summer2017 + , courseSchool = ifi , courseCapacity = Just 20 , courseHasRegistration = False , courseRegisterFrom = Nothing @@ -136,8 +136,8 @@ main = db $ do , courseDescription = Nothing , courseLinkExternal = Nothing , courseShorthand = "ixd" - , courseTermId = TermKey summer2018 - , courseSchoolId = ifi + , courseTerm = TermKey summer2018 + , courseSchool = ifi , courseCapacity = Just 20 , courseHasRegistration = True , courseRegisterFrom = Just now @@ -155,8 +155,8 @@ main = db $ do , courseDescription = Nothing , courseLinkExternal = Nothing , courseShorthand = "ux3" - , courseTermId = TermKey winter2017 - , courseSchoolId = ifi + , courseTerm = TermKey winter2017 + , courseSchool = ifi , courseCapacity = Just 30 , courseHasRegistration = False , courseRegisterFrom = Nothing @@ -174,8 +174,8 @@ main = db $ do , courseDescription = Nothing , courseLinkExternal = Nothing , courseShorthand = "pmo" - , courseTermId = TermKey summer2017 - , courseSchoolId = ifi + , courseTerm = TermKey summer2017 + , courseSchool = ifi , courseCapacity = Just 50 , courseHasRegistration = False , courseRegisterFrom = Nothing @@ -193,8 +193,8 @@ main = db $ do , courseDescription = Nothing , courseLinkExternal = Nothing , courseShorthand = "dbs" - , courseTermId = TermKey summer2018 - , courseSchoolId = ifi + , courseTerm = TermKey summer2018 + , courseSchool = ifi , courseCapacity = Just 50 , courseHasRegistration = False , courseRegisterFrom = Nothing diff --git a/models b/models index 3e0c09966..ae1518f40 100644 --- a/models +++ b/models @@ -1,10 +1,10 @@ User - plugin Text - ident Text + plugin Text + ident Text matrikelnummer Text Maybe - email Text - displayName Text - maxFavourites Int default=12 + email Text + displayName Text + maxFavourites Int default=12 UniqueAuthentication plugin ident UniqueEmail email UserAdmin @@ -52,20 +52,20 @@ DegreeCourse json terms StudyTermsId UniqueDegreeCourse course degree terms Course - name Text - description Html Maybe - linkExternal Text Maybe - shorthand Text - termId TermId - schoolId SchoolId - capacity Int Maybe + name Text + description Html Maybe + linkExternal Text Maybe + shorthand Text + term TermId + school SchoolId + capacity Int Maybe hasRegistration Bool -- canRegisterNow = hasRegistration && maybe False (<= currentTime) registerFrom && maybe True (>= currentTime) registerTo registerFrom UTCTime Maybe registerTo UTCTime Maybe deregisterUntil UTCTime Maybe registerSecret Text Maybe -- Falls ein Passwort erforderlich ist materialFree Bool default=true - CourseTermShort termId shorthand + CourseTermShort term shorthand CourseEdit user UserId time UTCTime @@ -76,29 +76,28 @@ CourseFavourite course CourseId UniqueCourseFavourite user course Lecturer - userId UserId - courseId CourseId - UniqueLecturer userId courseId + user UserId + course CourseId + UniqueLecturer user course Corrector - userId UserId - courseId CourseId + user UserId + course CourseId load Load -- SELECT submissionID FROM Tutorial, TutorialUser, Submission, Sheet -- WHERE ( tutorialTutor = correctorUserId -- && tutorialCourse = correctorCourseId -- && tutorialUserTutorial = tutorialId -- && submissionUser = tutorialUserUser - -- && sheetId = submissionSheetId + -- && sheetId = SubmissionSheet -- && sheetCourse = correctorCourseId -- ) - UniqueCorrector userId courseId CourseParticipant - courseId CourseId - userId UserId + course CourseId + user UserId registration UTCTime - UniqueParticipant userId courseId + UniqueParticipant user course Sheet - courseId CourseId + course CourseId name Text description Html Maybe type SheetType @@ -109,23 +108,27 @@ Sheet activeTo UTCTime hintFrom UTCTime Maybe solutionFrom UTCTime Maybe - CourseSheet courseId name + CourseSheet course name SheetEdit user UserId time UTCTime sheet SheetId +SheetCorrector + user UserId + sheet CourseId + load Load SheetFile - sheetId SheetId - fileId FileId + sheet SheetId + file FileId type SheetFileType - UniqueSheetFile fileId sheetId type + UniqueSheetFile file sheet type File title FilePath content ByteString Maybe -- Nothing iff this is a directory modified UTCTime deriving Show Eq Submission - sheetId SheetId + sheet SheetId ratingPoints Points Maybe ratingComment Text Maybe ratingBy UserId Maybe @@ -136,37 +139,37 @@ SubmissionEdit time UTCTime submission SubmissionId SubmissionFile - submissionId SubmissionId - fileId FileId + submission SubmissionId + file FileId isUpdate Bool isDeletion Bool - UniqueSubmissionFile fileId submissionId isUpdate + UniqueSubmissionFile file submission isUpdate deriving Show SubmissionUser - userId UserId - submissionId SubmissionId - UniqueSubmissionUser userId submissionId + user UserId + submission SubmissionId + UniqueSubmissionUser user submission SubmissionGroup - courseId CourseId + course CourseId name Text Maybe SubmissionGroupEdit user UserId time UTCTime submissionGroup SubmissionGroupId SubmissionGroupUser - submissionGroupId SubmissionGroupId - userId UserId - UniqueSubmissionGroupUser submissionGroupId userId + submissionGroup SubmissionGroupId + user UserId + UniqueSubmissionGroupUser submissionGroup user Tutorial json name Text - tutor UserId + tutor UserId course CourseId TutorialUser - userId UserId - tutorialId TutorialId - UniqueTutorialUser userId tutorialId + user UserId + tutorial TutorialId + UniqueTutorialUser user tutorial Booking - termId TermId + term TermId begin UTCTime end UTCTime weekly Bool @@ -183,17 +186,17 @@ Room building Text Maybe -- BookingRoom -- subject RoomForId --- roomId RoomId --- bookingId BookingId --- UniqueRoomCourse subject roomId bookingId +-- room RoomId +-- booking BookingId +-- UniqueRoomCourse subject room booking +RoomFor - courseId CourseId - tutorialId TutorialId - examId ExamId --- data RoomFor = RoomForCourseIdSum CourseId | RoomForTutorialIdSum TutorialId ... + course CourseId + tutorial TutorialId + exam ExamId +-- data RoomFor = RoomForCourseSum CourseId | RoomForTutorialSum TutorialId ... -- EXAMS ARE TODO: Exam - courseId CourseId + course CourseId name Text description Text begin UTCTime @@ -208,8 +211,8 @@ Exam -- time UTCTime -- exam ExamId --ExamUser --- userId UserId +-- user UserId -- examId ExamId -- -- CONTINUE HERE: Include rating in this table or separately? --- UniqueExamUser userId examId +-- UniqueExamUser user examId -- By default this file is used in Model.hs (which is imported by Foundation.hs) diff --git a/src/Foundation.hs b/src/Foundation.hs index ac6f25b6c..514f152bf 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -274,7 +274,7 @@ submissionAccess cID = do authId <- lift requireAuthId submissionId <- either decrypt decrypt cID Submission{..} <- get404 submissionId - submissionUsers <- map (submissionUserUserId . entityVal) <$> selectList [SubmissionUserSubmissionId ==. submissionId] [] + submissionUsers <- map (submissionUserUser . entityVal) <$> selectList [SubmissionUserSubmission ==. submissionId] [] let auth = authId `elem` submissionUsers || Just authId == submissionRatingBy return $ case auth of True -> Authorized @@ -304,8 +304,9 @@ lecturerAccess' = authorizedFor UniqueSchoolLecturer MsgUnauthorizedSchoolLectur courseLecturerAccess :: CourseId -> YesodDB UniWorX AuthResult courseLecturerAccess = authorizedFor UniqueLecturer MsgUnauthorizedLecturer -courseCorrectorAccess :: CourseId -> YesodDB UniWorX AuthResult -courseCorrectorAccess = authorizedFor UniqueCorrector MsgUnauthorizedCorrector +--courseCorrectorAccess :: CourseId -> YesodDB UniWorX AuthResult +--courseCorrectorAccess = authorizedFor UniqueCorrector MsgUnauthorizedCorrector +-- TODO: Correctors are no longer unit, could be ByTutorial and also by ByProportion courseParticipantAccess :: CourseId -> YesodDB UniWorX AuthResult courseParticipantAccess = authorizedFor UniqueParticipant MsgUnauthorizedParticipant diff --git a/src/Handler/Course.hs b/src/Handler/Course.hs index 1945f323a..b8edfee9a 100644 --- a/src/Handler/Course.hs +++ b/src/Handler/Course.hs @@ -30,7 +30,7 @@ getCourseListTermR :: TermId -> Handler Html getCourseListTermR tidini = do (term,courses) <- runDB $ (,) <$> get tidini - <*> selectList [CourseTermId ==. tidini] [Asc CourseShorthand] + <*> selectList [CourseTerm ==. tidini] [Asc CourseShorthand] when (isNothing term) $ do addMessage "warning" [shamlet| Semester #{toPathPiece tidini} nicht gefunden. |] redirect TermShowR @@ -39,20 +39,20 @@ getCourseListTermR tidini = do [ headed "Kürzel" $ (\ckv -> let c = entityVal ckv shd = courseShorthand c - tid = courseTermId c + tid = courseTerm c in [whamlet| #{shd} |] ) -- , headed "Institut" $ [shamlet| #{course} |] , headed "Beginn Anmeldung" $ fromString.(maybe "" formatTimeGerWD).courseRegisterFrom.entityVal , headed "Ende Anmeldung" $ fromString.(maybe "" formatTimeGerWD).courseRegisterTo.entityVal , headed "Teilnehmer" $ (\ckv -> do let cid = entityKey ckv - partiNum <- handlerToWidget $ runDB $ count [CourseParticipantCourseId ==. cid] + partiNum <- handlerToWidget $ runDB $ count [CourseParticipantCourse ==. cid] [whamlet| #{show partiNum} |] ) , headed " " $ (\ckv -> let c = entityVal ckv shd = courseShorthand c - tid = courseTermId c + tid = courseTerm c in do adminLink <- handlerToWidget $ isAuthorized (CourseR tid shd CourseEditR) False -- if (adminLink==Authorized) then linkButton "Ändern" BCWarning (CourseEditR tid shd) else "" @@ -82,8 +82,8 @@ getCourseShowR tid csh = do (courseEnt,(schoolMB,participants,mbRegistered)) <- runDB $ do courseEnt@(Entity cid course) <- getBy404 $ CourseTermShort tid csh dependent <- (,,) - <$> get (courseSchoolId course) -- join - <*> count [CourseParticipantCourseId ==. cid] -- join + <$> get (courseSchool course) -- join + <*> count [CourseParticipantCourse ==. cid] -- join <*> (case mbAid of -- TODO: Someone please refactor this late-night mess here! Nothing -> return False (Just aid) -> do @@ -183,8 +183,8 @@ courseEditHandler course = do , courseDescription = cfDesc res , courseLinkExternal = cfLink res , courseShorthand = cfShort res - , courseTermId = cfTerm res - , courseSchoolId = cfSchool res + , courseTerm = cfTerm res + , courseSchool = cfSchool res , courseCapacity = cfCapacity res , courseHasRegistration = cfHasReg res , courseRegisterFrom = cfRegFrom res @@ -226,8 +226,8 @@ courseEditHandler course = do -- , CourseDescription =. cfDesc res -- , CourseLinkExternal =. cfLink res -- , CourseShorthand =. cfShort res -- TODO: change here should generate a warning, or only allowed for Admins?! --- , CourseTermId =. tid -- TODO: change here should generate a warning, or only allowed for Admins?! --- , CourseSchoolId =. cfSchool res +-- , CourseTerm =. tid -- TODO: change here should generate a warning, or only allowed for Admins?! +-- , CourseSchool =. cfSchool res -- , CourseCapacity =. cfCapacity res -- , CourseRegisterFrom =. cfRegFrom res -- , CourseRegisterTo =. cfRegTo res @@ -239,8 +239,8 @@ courseEditHandler course = do , courseDescription = cfDesc res , courseLinkExternal = cfLink res , courseShorthand = cfShort res - , courseTermId = cfTerm res - , courseSchoolId = cfSchool res + , courseTerm = cfTerm res + , courseSchool = cfSchool res , courseCapacity = cfCapacity res , courseHasRegistration = cfHasReg res , courseRegisterFrom = cfRegFrom res @@ -291,8 +291,8 @@ courseToForm cEntity = CourseForm , cfDesc = courseDescription course , cfLink = courseLinkExternal course , cfShort = courseShorthand course - , cfTerm = courseTermId course - , cfSchool = courseSchoolId course + , cfTerm = courseTerm course + , cfSchool = courseSchool course , cfCapacity = courseCapacity course , cfHasReg = courseHasRegistration course , cfRegFrom = courseRegisterFrom course diff --git a/src/Handler/CryptoIDDispatch.hs b/src/Handler/CryptoIDDispatch.hs index c604d3e45..f5a77cdbd 100644 --- a/src/Handler/CryptoIDDispatch.hs +++ b/src/Handler/CryptoIDDispatch.hs @@ -34,10 +34,10 @@ instance CryptoRoute UUID SubmissionId where cryptoIDRoute _ (CryptoID -> cID) = do (smid :: SubmissionId) <- decrypt cID (tid,csh,shn) <- runDB $ do - shid <- submissionSheetId <$> get404 smid + shid <- submissionSheet <$> get404 smid Sheet{..} <- get404 shid - Course{..} <- get404 sheetCourseId - return (courseTermId, courseShorthand, sheetName) + Course{..} <- get404 sheetCourse + return (courseTerm, courseShorthand, sheetName) return $ CourseR tid csh $ SheetR $ SubmissionR shn $ SubmissionMode $ Just cID diff --git a/src/Handler/Sheet.hs b/src/Handler/Sheet.hs index 455662401..d0da62b13 100644 --- a/src/Handler/Sheet.hs +++ b/src/Handler/Sheet.hs @@ -68,8 +68,8 @@ makeSheetForm :: Maybe SheetId -> Maybe SheetForm -> Form SheetForm makeSheetForm msId template = identForm FIDsheet $ \html -> do let oldFileIds fType | Just sId <- msId = fmap setFromList . fmap (map E.unValue) . runDB . E.select . E.from $ \(file `E.InnerJoin` sheetFile) -> do - E.on $ file E.^. FileId E.==. sheetFile E.^. SheetFileFileId - E.where_ $ sheetFile E.^. SheetFileSheetId E.==. E.val sId + E.on $ file E.^. FileId E.==. sheetFile E.^. SheetFileFile + E.where_ $ sheetFile E.^. SheetFileSheet E.==. E.val sId E.&&. sheetFile E.^. SheetFileType E.==. E.val fType return (file E.^. FileId) | otherwise = return Set.empty @@ -135,11 +135,11 @@ getSheetList courseEnt = do let cid = entityKey courseEnt let course = entityVal courseEnt let csh = courseShorthand course - let tid = courseTermId course + let tid = courseTerm course sheets <- runDB $ do - rawSheets <- selectList [SheetCourseId ==. cid] [Desc SheetActiveFrom] + rawSheets <- selectList [SheetCourse ==. cid] [Desc SheetActiveFrom] forM rawSheets $ \(Entity sid sheet) -> do - let sheetsub = [SubmissionSheetId ==. sid] + let sheetsub = [SubmissionSheet ==. sid] submissions <- count sheetsub rated <- count $ (SubmissionRatingTime !=. Nothing):sheetsub return (sid, sheet, (submissions, rated)) @@ -188,8 +188,8 @@ getSheetShowR tid csh shn = do fileNameTypes <- runDB $ E.select $ E.from $ \(sheet `E.InnerJoin` sheetFile `E.InnerJoin` file) -> do -- Restrict to consistent rows that correspond to each other - E.on (file E.^. FileId E.==. sheetFile E.^. SheetFileFileId) - E.on (sheetFile E.^. SheetFileSheetId E.==. sheet E.^. SheetId) + E.on (file E.^. FileId E.==. sheetFile E.^. SheetFileFile) + E.on (sheetFile E.^. SheetFileSheet E.==. sheet E.^. SheetId) -- filter to requested file E.where_ (sheet E.^. SheetId E.==. E.val sid ) -- return desired columns @@ -214,15 +214,15 @@ getSheetFileR tid csh shn typ title = do content <- runDB $ E.select $ E.from $ \(course `E.InnerJoin` sheet `E.InnerJoin` sheetFile `E.InnerJoin` file) -> do -- Restrict to consistent rows that correspond to each other - E.on (file E.^. FileId E.==. sheetFile E.^. SheetFileFileId) - E.on (sheetFile E.^. SheetFileSheetId E.==. sheet E.^. SheetId) - E.on (sheet E.^. SheetCourseId E.==. course E.^. CourseId) + E.on (file E.^. FileId E.==. sheetFile E.^. SheetFileFile) + E.on (sheetFile E.^. SheetFileSheet E.==. sheet E.^. SheetId) + E.on (sheet E.^. SheetCourse E.==. course E.^. CourseId) -- filter to requested file E.where_ ((file E.^. FileTitle E.==. E.val title) E.&&. (sheetFile E.^. SheetFileType E.==. E.val typ ) E.&&. (sheet E.^. SheetName E.==. E.val shn ) E.&&. (course E.^. CourseShorthand E.==. E.val csh ) - E.&&. (course E.^. CourseTermId E.==. E.val tid ) + E.&&. (course E.^. CourseTerm E.==. E.val tid ) ) -- return desired columns return $ file E.^. FileContent @@ -251,8 +251,8 @@ getSheetEditR tid csh shn = do (sheetEnt, sheetFileIds) <- runDB $ do ent <- fetchSheet tid csh shn fIds <- fmap setFromList . fmap (map E.unValue) . E.select . E.from $ \(file `E.InnerJoin` sheetFile) -> do - E.on $ file E.^. FileId E.==. sheetFile E.^. SheetFileFileId - E.where_ $ sheetFile E.^. SheetFileSheetId E.==. E.val (entityKey ent) + E.on $ file E.^. FileId E.==. sheetFile E.^. SheetFileFile + E.where_ $ sheetFile E.^. SheetFileSheet E.==. E.val (entityKey ent) E.&&. sheetFile E.^. SheetFileType E.==. E.val SheetExercise return (file E.^. FileId) return (ent, fIds) @@ -295,7 +295,7 @@ handleSheetEdit tid csh msId template dbAction = do actTime <- liftIO getCurrentTime cid <- getKeyBy404 $ CourseTermShort tid csh let newSheet = Sheet - { sheetCourseId = cid + { sheetCourse = cid , sheetName = sfName , sheetDescription = sfDescription , sheetType = sfType @@ -345,7 +345,7 @@ getSheetDelR tid csh shn = do _other -> do submissionno <- runDB $ do sid <- fetchSheetId tid csh shn - count [SubmissionSheetId ==. sid] + count [SubmissionSheet ==. sid] let formTitle = MsgSheetDelTitle tident csh shn let formText = Just $ MsgSheetDelText submissionno let actionUrl = CSheetR tid csh $ SheetDelR shn @@ -369,8 +369,8 @@ insertSheetFile sid ftype finfo = do insertSheetFile' :: SheetId -> SheetFileType -> Source Handler (Either FileId File) -> YesodDB UniWorX () insertSheetFile' sid ftype fs = do oldFileIds <- fmap setFromList . fmap (map E.unValue) . E.select . E.from $ \(file `E.InnerJoin` sheetFile) -> do - E.on $ file E.^. FileId E.==. sheetFile E.^. SheetFileFileId - E.where_ $ sheetFile E.^. SheetFileSheetId E.==. E.val sid + E.on $ file E.^. FileId E.==. sheetFile E.^. SheetFileFile + E.where_ $ sheetFile E.^. SheetFileSheet E.==. E.val sid E.&&. sheetFile E.^. SheetFileType E.==. E.val ftype return (file E.^. FileId) keep <- execWriterT . runConduit $ transPipe (lift . lift) fs =$= C.mapM_ finsert diff --git a/src/Handler/Submission.hs b/src/Handler/Submission.hs index ac25a999f..0a78a8458 100644 --- a/src/Handler/Submission.hs +++ b/src/Handler/Submission.hs @@ -80,27 +80,27 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do case msmid of Nothing -> do submissions <- E.select . E.from $ \(submission `E.InnerJoin` submissionUser) -> do - E.on (submission E.^. SubmissionId E.==. submissionUser E.^. SubmissionUserSubmissionId) - E.where_ $ submissionUser E.^. SubmissionUserUserId E.==. E.val uid - E.&&. submission E.^. SubmissionSheetId E.==. E.val shid + E.on (submission E.^. SubmissionId E.==. submissionUser E.^. SubmissionUserSubmission) + E.where_ $ submissionUser E.^. SubmissionUserUser E.==. E.val uid + E.&&. submission E.^. SubmissionSheet E.==. E.val shid return $ submission E.^. SubmissionId -- $logDebugS "Submission.DUPLICATENEW" (tshow submissions) case submissions of [] -> do -- fetch buddies from previous submission in this course buddies <- E.select . E.from $ \(submissionUser `E.InnerJoin` user) -> do - E.on (submissionUser E.^. SubmissionUserUserId E.==. user E.^. UserId) + E.on (submissionUser E.^. SubmissionUserUser E.==. user E.^. UserId) let oldids = E.subList_select . E.from $ \(sheet `E.InnerJoin` submission `E.InnerJoin` submissionUser `E.InnerJoin` submissionEdit) -> do E.on (submissionEdit E.^. SubmissionEditSubmission E.==. submission E.^. SubmissionId) - E.on (submissionUser E.^. SubmissionUserSubmissionId E.==. submission E.^. SubmissionId) - E.on (sheet E.^. SheetId E.==. submission E.^. SubmissionSheetId) - E.where_ $ submissionUser E.^. SubmissionUserUserId E.==. E.val uid - E.&&. sheet E.^. SheetCourseId E.==. E.val sheetCourseId + E.on (submissionUser E.^. SubmissionUserSubmission E.==. submission E.^. SubmissionId) + E.on (sheet E.^. SheetId E.==. submission E.^. SubmissionSheet) + E.where_ $ submissionUser E.^. SubmissionUserUser E.==. E.val uid + E.&&. sheet E.^. SheetCourse E.==. E.val sheetCourse E.orderBy [E.desc $ submissionEdit E.^. SubmissionEditTime] E.limit 1 return $ submission E.^. SubmissionId - E.where_ $ submissionUser E.^. SubmissionUserSubmissionId `E.in_` oldids - E.&&. submissionUser E.^. SubmissionUserUserId E.!=. E.val uid + E.where_ $ submissionUser E.^. SubmissionUserSubmission `E.in_` oldids + E.&&. submissionUser E.^. SubmissionUserUser E.!=. E.val uid E.orderBy [E.asc $ user E.^. UserEmail] return $ user E.^. UserEmail return (sheet,buddies,[],[]) @@ -109,13 +109,13 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do addMessageI "info" $ MsgSubmissionAlreadyExists redirect $ CourseR tid csh $ SheetR $ SubmissionR shn $ SubmissionMode $ Just cID (Just smid) -> do - shid' <- submissionSheetId <$> get404 smid + shid' <- submissionSheet <$> get404 smid when (shid /= shid') $ invalidArgsI [MsgSubmissionWrongSheet] -- fetch buddies from current submission buddies <- E.select . E.from $ \(submissionUser `E.InnerJoin` user) -> do - E.on (submissionUser E.^. SubmissionUserUserId E.==. user E.^. UserId) - E.where_ $ submissionUser E.^. SubmissionUserSubmissionId E.==. E.val smid - E.&&. submissionUser E.^. SubmissionUserUserId E.!=. E.val uid + E.on (submissionUser E.^. SubmissionUserUser E.==. user E.^. UserId) + E.where_ $ submissionUser E.^. SubmissionUserSubmission E.==. E.val smid + E.&&. submissionUser E.^. SubmissionUserUser E.!=. E.val uid E.orderBy [E.asc $ user E.^. UserEmail] return $ user E.^. UserEmail oldfiles <- sourceToList $ submissionFileSource smid @@ -145,13 +145,13 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do E.where_ $ (E.lower_ $ user E.^. UserEmail) `E.in_` E.valList gemails let isParticipant = E.sub_select . E.from $ \courseParticipant -> do - E.where_ $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUserId - E.&&. courseParticipant E.^. CourseParticipantCourseId E.==. E.val sheetCourseId + E.where_ $ user E.^. UserId E.==. courseParticipant E.^. CourseParticipantUser + E.&&. courseParticipant E.^. CourseParticipantCourse E.==. E.val sheetCourse return $ E.countRows E.>. E.val (0 :: Int64) hasSubmitted = E.sub_select . E.from $ \(submissionUser `E.InnerJoin` submission) -> do - E.on $ submissionUser E.^. SubmissionUserSubmissionId E.==. submission E.^. SubmissionId - E.where_ $ submissionUser E.^. SubmissionUserUserId E.==. user E.^. UserId - E.&&. submission E.^. SubmissionSheetId E.==. E.val shid + E.on $ submissionUser E.^. SubmissionUserSubmission E.==. submission E.^. SubmissionId + E.where_ $ submissionUser E.^. SubmissionUserUser E.==. user E.^. UserId + E.&&. submission E.^. SubmissionSheet E.==. E.val shid return $ E.countRows E.>. E.val (0 :: Int64) return (user E.^. UserEmail, (user E.^. UserId, isParticipant, hasSubmitted)) $logDebugS "SUBMISSION.AdHocGroupValidation" $ tshow participants @@ -180,11 +180,11 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do -- Determine members of pre-registered group groupUids <- fmap (setFromList . map E.unValue) . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup `E.InnerJoin` submissionGroupUser') -> do - E.on $ submissionGroup E.^. SubmissionGroupId E.==. submissionGroupUser' E.^. SubmissionGroupUserSubmissionGroupId - E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroupId E.==. submissionGroup E.^. SubmissionGroupId - E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUserId E.==. E.val uid - E.&&. submissionGroup E.^. SubmissionGroupCourseId E.==. E.val sheetCourseId - return $ submissionGroupUser' E.^. SubmissionGroupUserUserId + E.on $ submissionGroup E.^. SubmissionGroupId E.==. submissionGroupUser' E.^. SubmissionGroupUserSubmissionGroup + E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId + E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUser E.==. E.val uid + E.&&. submissionGroup E.^. SubmissionGroupCourse E.==. E.val sheetCourse + return $ submissionGroupUser' E.^. SubmissionGroupUserUser -- SubmissionUser for all group members (pre-registered & ad-hoc) forM_ (groupUids `Set.union` adhocIds) $ \uid' -> void . insertUnique $ SubmissionUser uid' smid @@ -229,8 +229,8 @@ postSubmissionR tid csh shn (SubmissionMode mcid) = do submissionFileSource :: SubmissionId -> Source (YesodDB UniWorX) (Entity File) submissionFileSource submissionID = E.selectSource . E.from $ \(sf `E.InnerJoin` f) -> E.distinctOnOrderBy [E.asc $ f E.^. FileTitle] $ do - E.on (f E.^. FileId E.==. sf E.^. SubmissionFileFileId) - E.where_ $ sf E.^. SubmissionFileSubmissionId E.==. E.val submissionID + E.on (f E.^. FileId E.==. sf E.^. SubmissionFileFile) + E.where_ $ sf E.^. SubmissionFileSubmission E.==. E.val submissionID E.where_ . E.not_ $ sf E.^. SubmissionFileIsDeletion E.orderBy [E.desc $ sf E.^. SubmissionFileIsUpdate] return f @@ -248,8 +248,8 @@ getSubmissionDownloadSingleR cID path = do maybe notFound (return . toTypedContent . Text.decodeUtf8) $ fileContent =<< file False -> do results <- E.select . E.from $ \(sf `E.InnerJoin` f) -> E.distinctOnOrderBy [E.asc $ f E.^. FileTitle] $ do - E.on (f E.^. FileId E.==. sf E.^. SubmissionFileFileId) - E.where_ (sf E.^. SubmissionFileSubmissionId E.==. E.val submissionID) + E.on (f E.^. FileId E.==. sf E.^. SubmissionFileFile) + E.where_ (sf E.^. SubmissionFileSubmission E.==. E.val submissionID) E.where_ (f E.^. FileTitle E.==. E.val path) E.where_ . E.not_ . E.isNothing $ f E.^. FileContent E.where_ . E.not_ $ sf E.^. SubmissionFileIsDeletion @@ -288,8 +288,8 @@ getSubmissionDownloadArchiveR (ZIPArchiveName cID) = do submissionTable :: MForm Handler (FormResult [SubmissionId], Widget) submissionTable = do subs <- lift . runDB $ E.select . E.from $ \(sub `E.InnerJoin` sheet `E.InnerJoin` course) -> do - E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourseId - E.on $ sheet E.^. SheetId E.==. sub E.^. SubmissionSheetId + E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse + E.on $ sheet E.^. SheetId E.==. sub E.^. SubmissionSheet return (sub, sheet, course) @@ -297,7 +297,7 @@ submissionTable = do (,,) <$> encrypt submissionId <*> encrypt submissionId <*> pure s let - anchorCourse (_, _, (_, _, Entity _ Course{..})) = CourseR courseTermId courseShorthand CourseShowR + anchorCourse (_, _, (_, _, Entity _ Course{..})) = CourseR courseTerm courseShorthand CourseShowR courseText (_, _, (_, _, Entity _ Course{..})) = toWidget courseName anchorSubmission (_, cUUID, _) = SubmissionDemoR cUUID submissionText (cID, _, _) = toWidget . toPathPiece . CI.foldedCase $ ciphertext cID @@ -334,7 +334,7 @@ postSubmissionListR = do Just sink -> return sink Nothing -> do Submission{..} <- lift $ get404 sId - return . newResumableSink $ sinkSubmission submissionSheetId userId (Just (sId, isUpdate)) + return . newResumableSink $ sinkSubmission submissionSheet userId (Just (sId, isUpdate)) sink' <- lift $ yield val ++$$ sink case sink' of Left _ -> error "sinkSubmission returned prematurely" @@ -434,12 +434,12 @@ postSubmissionDemoR cID = do yieldM $ do fileContent <- Just <$> runConduit (fileSource fInfo =$= foldC) return File{..} - submissionId' <- runConduit $ source =$= extractRatings =$= sinkSubmission submissionSheetId userId (Just (submissionId, isUpdate)) + submissionId' <- runConduit $ source =$= extractRatings =$= sinkSubmission submissionSheet userId (Just (submissionId, isUpdate)) get404 submissionId' files <- E.select . E.from $ \(sf `E.InnerJoin` f) -> E.distinctOnOrderBy [E.asc $ f E.^. FileTitle] $ do - E.on (f E.^. FileId E.==. sf E.^. SubmissionFileFileId) - E.where_ (sf E.^. SubmissionFileSubmissionId E.==. E.val submissionId) + E.on (f E.^. FileId E.==. sf E.^. SubmissionFileFile) + E.where_ (sf E.^. SubmissionFileSubmission E.==. E.val submissionId) E.orderBy [E.desc $ sf E.^. SubmissionFileIsUpdate] return (f, sf) return (submission, files) diff --git a/src/Handler/Term.hs b/src/Handler/Term.hs index 46f464cc1..59c9e0909 100644 --- a/src/Handler/Term.hs +++ b/src/Handler/Term.hs @@ -35,7 +35,7 @@ getTermShowR = do -- E.orderBy [E.desc $ term E.^. TermStart ] let courseCount :: E.SqlExpr (E.Value Int) courseCount = E.sub_select . E.from $ \course -> do - E.where_ $ term E.^. TermId E.==. course E.^. CourseTermId + E.where_ $ term E.^. TermId E.==. course E.^. CourseTerm return E.countRows return (term, courseCount) selectRep $ do diff --git a/src/Handler/Utils/Rating.hs b/src/Handler/Utils/Rating.hs index e90c9501c..fe4c4b014 100644 --- a/src/Handler/Utils/Rating.hs +++ b/src/Handler/Utils/Rating.hs @@ -90,8 +90,8 @@ instance Exception RatingException getRating :: SubmissionId -> YesodDB UniWorX (Maybe Rating) getRating submissionId = runMaybeT $ do let query = E.select . E.from $ \(submission `E.InnerJoin` sheet `E.InnerJoin` course) -> do - E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourseId - E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheetId + E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse + E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet E.where_ $ submission E.^. SubmissionId E.==. E.val submissionId diff --git a/src/Handler/Utils/Sheet.hs b/src/Handler/Utils/Sheet.hs index 24db7ae1a..76fed4737 100644 --- a/src/Handler/Utils/Sheet.hs +++ b/src/Handler/Utils/Sheet.hs @@ -33,8 +33,8 @@ fetchSheetAux prj tid csh shn = -- getBy404 $ CourseSheet cid shn -- Mit Esqueleto: sheetList <- E.select . E.from $ \(course `E.InnerJoin` sheet) -> do - E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourseId - E.where_ $ course E.^. CourseTermId E.==. E.val tid + E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse + E.where_ $ course E.^. CourseTerm E.==. E.val tid E.&&. course E.^. CourseShorthand E.==. E.val csh E.&&. sheet E.^. SheetName E.==. E.val shn return $ prj sheet @@ -49,4 +49,4 @@ fetchSheetId :: TermId -> Text -> Text -> YesodDB UniWorX (Key Sheet) fetchSheetId tid cid shn = E.unValue <$> fetchSheetAux (E.^. SheetId) tid cid shn fetchSheetIdCourseId :: TermId -> Text -> Text -> YesodDB UniWorX (Key Sheet, Key Course) -fetchSheetIdCourseId tid cid shn = bimap E.unValue E.unValue <$> fetchSheetAux ((,) <$> (E.^. SheetId) <*> (E.^. SheetCourseId)) tid cid shn +fetchSheetIdCourseId tid cid shn = bimap E.unValue E.unValue <$> fetchSheetAux ((,) <$> (E.^. SheetId) <*> (E.^. SheetCourse)) tid cid shn diff --git a/src/Handler/Utils/Submission.hs b/src/Handler/Utils/Submission.hs index 6a100ab98..9b71290a3 100644 --- a/src/Handler/Utils/Submission.hs +++ b/src/Handler/Utils/Submission.hs @@ -65,7 +65,7 @@ sinkSubmission :: SheetId sinkSubmission sheetId userId mExists = do now <- liftIO getCurrentTime let - submissionSheetId = sheetId + submissionSheet = sheetId submissionRatingPoints = Nothing submissionRatingComment = Nothing submissionRatingBy = Nothing @@ -90,8 +90,8 @@ sinkSubmission sheetId userId mExists = do tell $ mempty{ sinkFilenames = Set.singleton fileTitle } otherVersions <- lift . E.select . E.from $ \(sf `E.InnerJoin` f) -> do - E.on $ sf E.^. SubmissionFileFileId E.==. f E.^. FileId - E.where_ $ sf E.^. SubmissionFileSubmissionId E.==. E.val submissionId + E.on $ sf E.^. SubmissionFileFile E.==. f E.^. FileId + E.where_ $ sf E.^. SubmissionFileSubmission E.==. E.val submissionId -- E.where_ $ sf E.^. SubmissionFileIsUpdate E.==. E.val isUpdate E.where_ $ f E.^. FileTitle E.==. E.val fileTitle -- 'Zip.hs' normalises filenames already, so this should work return (f, sf) @@ -121,8 +121,8 @@ sinkSubmission sheetId userId mExists = do _ -> do fileId <- insert file insert_ $ SubmissionFile - { submissionFileSubmissionId = submissionId - , submissionFileFileId = fileId + { submissionFileSubmission = submissionId + , submissionFileFile = fileId , submissionFileIsUpdate = isUpdate , submissionFileIsDeletion = False } @@ -189,8 +189,8 @@ sinkSubmission sheetId userId mExists = do finalize :: SubmissionSinkState -> YesodDB UniWorX () finalize SubmissionSinkState{..} = do missingFiles <- E.select . E.from $ \(sf `E.InnerJoin` f) -> E.distinctOnOrderBy [E.asc $ f E.^. FileTitle] $ do - E.on $ sf E.^. SubmissionFileFileId E.==. f E.^. FileId - E.where_ $ sf E.^. SubmissionFileSubmissionId E.==. E.val submissionId + E.on $ sf E.^. SubmissionFileFile E.==. f E.^. FileId + E.where_ $ sf E.^. SubmissionFileSubmission E.==. E.val submissionId when (not isUpdate) $ E.where_ $ sf E.^. SubmissionFileIsUpdate E.==. E.val isUpdate E.where_ $ f E.^. FileTitle `E.notIn` E.valList (Set.toList sinkFilenames) @@ -202,8 +202,8 @@ sinkSubmission sheetId userId mExists = do False -> deleteCascadeWhere [ FileId <-. [ fileId | (Entity fileId _, _) <- missingFiles ] ] True -> forM_ missingFiles $ \(Entity fileId File{..}, Entity sfId SubmissionFile{..}) -> do shadowing <- E.select . E.from $ \(sf `E.InnerJoin` f) -> do - E.on $ sf E.^. SubmissionFileFileId E.==. f E.^. FileId - E.where_ $ sf E.^. SubmissionFileSubmissionId E.==. E.val submissionId + E.on $ sf E.^. SubmissionFileFile E.==. f E.^. FileId + E.where_ $ sf E.^. SubmissionFileSubmission E.==. E.val submissionId E.where_ $ sf E.^. SubmissionFileIsUpdate E.==. E.val (not isUpdate) E.where_ $ f E.^. FileTitle E.==. E.val fileTitle return $ f E.^. FileId @@ -212,13 +212,13 @@ sinkSubmission sheetId userId mExists = do ([], _) -> deleteCascade fileId (E.Value f:_, False) -> do insert_ $ SubmissionFile - { submissionFileSubmissionId = submissionId - , submissionFileFileId = f + { submissionFileSubmission = submissionId + , submissionFileFile = f , submissionFileIsUpdate = True , submissionFileIsDeletion = True } (E.Value f:_, True) -> do - update sfId [ SubmissionFileFileId =. f, SubmissionFileIsDeletion =. True ] + update sfId [ SubmissionFileFile =. f, SubmissionFileIsDeletion =. True ] deleteCascade fileId when (isUpdate && not (getAny sinkSeenRating)) $ diff --git a/templates/widgets/asidenav.hamlet b/templates/widgets/asidenav.hamlet index b19ab9d4f..29a3f1617 100644 --- a/templates/widgets/asidenav.hamlet +++ b/templates/widgets/asidenav.hamlet @@ -18,7 +18,7 @@ $newline never WiSe 17/18

Do 18.04.18 NotGraded
  -   -   -   - + Neues Übungsblatt anlegen

Übungsgruppen @@ -87,16 +83,14 @@

Anmeldung bis
- Gruppe 1 + Gruppe 1 Montag 10:00 - 12:00 N/A 2/10 Tutor1 Tutoren Do 21.02.2019, 19:00
- Gruppe 2 + Gruppe 2 Montag 12:00 - 14:00 N/A 0/10 diff --git a/templates/default-layout.lucius b/templates/default-layout.lucius index abfcc19a2..16a7599c0 100644 --- a/templates/default-layout.lucius +++ b/templates/default-layout.lucius @@ -244,4 +244,3 @@ a.btn.btn-info:hover, .alert-debug { background-color: rgb(240, 30, 240); } - diff --git a/templates/standalone/inputs.lucius b/templates/standalone/inputs.lucius index 1ef824d13..71402a4b5 100644 --- a/templates/standalone/inputs.lucius +++ b/templates/standalone/inputs.lucius @@ -44,26 +44,12 @@ input[type="password"], input[type="url"], input[type="number"], input[type="email"] { - /* - background-color: rgba(0, 0, 0, 0.05); - padding: 7px 3px 7px; - outline: 0; - border: 0; - border-bottom: 2px solid var(--darkbase); - box-shadow: 0 2px 13px rgba(0, 0, 0, 0.05); - color: var(--fontbase); - transition: all .1s; - font-size: 16px; - min-width: 400px; - */ - - /* from bulma */ + /* from bulma.css */ background-color: #fff; border-color: #dbdbdb; color: #363636; - box-shadow: inset 0 1px 2px rgba(10,10,10,.1); + box-shadow: inset 0 2px 3px 1px rgba(50,50,50,.1); min-width: 400px; - width: 100%; -webkit-appearance: none; align-items: center; border: 1px solid transparent; @@ -83,6 +69,12 @@ input[type="email"] { .form-group--required { + label::before { + content: '*'; + position: absolute; + left: -14px; + } + input, textarea { border-bottom-color: var(--lighterbase); } From f4b6d1e3f5529ca2e9afab9a9d87ba609c54ba04 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Thu, 7 Jun 2018 11:26:41 +0200 Subject: [PATCH 066/559] Introduce pageHeading function --- messages/de.msg | 1 + src/Foundation.hs | 11 +++++++++-- templates/default-layout.hamlet | 5 +++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/messages/de.msg b/messages/de.msg index 5332201d5..a4e8637fa 100644 --- a/messages/de.msg +++ b/messages/de.msg @@ -49,3 +49,4 @@ SubmissionAlreadyExistsFor user@Text: #{user} hat bereits eine Abgabe zu diesem EMailUnknown email@Text: E-Mail #{email} gehört zu keinem bekannten Benutzer. NotAParticipant user@Text tid@TermIdentifier csh@Text: #{user} ist nicht im Kurs #{termToText tid}-#{csh} angemeldet. +HomeHeading: Startseite \ No newline at end of file diff --git a/src/Foundation.hs b/src/Foundation.hs index 70c4327a5..c6a43c77b 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -434,13 +434,14 @@ instance Yesod UniWorX where -- value passed to hamletToRepHtml cannot be a widget, this allows -- you to use normal widget features in default-layout. + let navbar :: Widget navbar = $(widgetFile "widgets/navbar") asidenav :: Widget asidenav = $(widgetFile "widgets/asidenav") - contentHeadline :: Maybe String - contentHeadline = Just "Some Headline" -- should be coming from the currently viewed page + contentHeadline :: Maybe Widget + contentHeadline = pageHeading =<< mcurrentRoute breadcrumbs :: Widget breadcrumbs = $(widgetFile "widgets/breadcrumbs") pageactionprime :: Widget @@ -686,6 +687,12 @@ pageActions (TermCourseListR _) = pageActions _ = [] +pageHeading :: Route UniWorx -> Maybe Widget +pageHeading HomeR + = Just [whamlet|_{MsgHomeHeading}|] +pageHeading _ + = Nothing + defaultLinks :: [MenuTypes] defaultLinks = -- Define the menu items of the header. [ NavbarRight $ MenuItem diff --git a/templates/default-layout.hamlet b/templates/default-layout.hamlet index 99b24bfca..165096dfd 100644 --- a/templates/default-layout.hamlet +++ b/templates/default-layout.hamlet @@ -14,8 +14,9 @@
#{msg} -

- #{fromMaybe "default headline" contentHeadline} + $maybe headline <- contentHeadline +

+ ^{headline} From 18c3840276432ec62ae1cfc679352009cd5b0d65 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Thu, 7 Jun 2018 11:36:59 +0200 Subject: [PATCH 067/559] Fix build --- src/Foundation.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Foundation.hs b/src/Foundation.hs index c6a43c77b..340285e0f 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -1,7 +1,7 @@ {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedLists #-} -{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TemplateHaskell, QuasiQuotes #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE ViewPatterns #-} @@ -687,7 +687,7 @@ pageActions (TermCourseListR _) = pageActions _ = [] -pageHeading :: Route UniWorx -> Maybe Widget +pageHeading :: Route UniWorX -> Maybe Widget pageHeading HomeR = Just [whamlet|_{MsgHomeHeading}|] pageHeading _ From c5beac0eb146cb7a7a5979fb8135954d710ba34d Mon Sep 17 00:00:00 2001 From: SJost Date: Thu, 7 Jun 2018 11:46:01 +0200 Subject: [PATCH 068/559] Minor commenting --- src/Foundation.hs | 89 ----------------------------------------------- 1 file changed, 89 deletions(-) diff --git a/src/Foundation.hs b/src/Foundation.hs index b29f42d2a..ed93e5a90 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -511,95 +511,6 @@ instance Yesod UniWorX where makeLogger = return . appLogger -{- ALL DEPRECATED and will be deleted, once knownTags is completed - -isAuthorizedDB :: Route UniWorX -> Bool -> YesodDB UniWorX AuthResult -isAuthorizedDB route@(routeAttrs -> attrs) writeable - | "adminAny" `member` attrs = adminAccess Nothing - | "lecturerAny" `member` attrs = lecturerAccess Nothing - -isAuthorizedDB UsersR _ = adminAccess Nothing -isAuthorizedDB (SubmissionDemoR cID) _ = return Authorized -- submissionAccess $ Right cID -isAuthorizedDB (SubmissionDownloadSingleR cID _) _ = submissionAccess $ Right cID -isAuthorizedDB (SubmissionDownloadArchiveR (ZIPArchiveName cID)) _ = submissionAccess $ Left cID -isAuthorizedDB TermEditR _ = adminAccess Nothing -isAuthorizedDB (TermEditExistR _) _ = adminAccess Nothing -isAuthorizedDB CourseNewR _ = lecturerAccess Nothing -isAuthorizedDB (CourseR t c CEditR) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) -isAuthorizedDB (CourseR t c (SheetR SheetListR)) False = return Authorized -- -isAuthorizedDB (CourseR t c (SheetR SheetListR)) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) -isAuthorizedDB (CourseR t c (SheetR (SShowR s))) _ = return Authorized -- TODO: nur für angemeldete Kursteilnehmer falls sichtbar, sonst nur Lectrurer oder Korrektor -isAuthorizedDB (CourseR t c (SheetR (SheetFileR s _ _))) _ = return Authorized -- TODO: nur für angemeldete Kursteilnehmer falls sichtbar, sonst nur Lectrurer oder Korrektor -isAuthorizedDB (CourseR t c (SheetR SheetNewR)) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) -isAuthorizedDB (CourseR t c (SheetR (SEditR s))) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) -isAuthorizedDB (CourseR t c (SheetR (SDelR s))) _ = courseLecturerAccess . entityKey =<< getBy404 (CourseTermShort t c) -isAuthorizedDB (CourseR t c (SheetR (SubmissionR s m))) _ = return Authorized -- TODO -- submissionAccess $ Right cID -isAuthorizedDB (CourseEditIDR cID) _ = do - courseId <- decrypt cID - courseLecturerAccess courseId -isAuthorizedDB _route _isWrite = return $ Unauthorized "No access to this route." -- Calling isAuthorized here creates infinite loop! - -submissionAccess :: Either CryptoFileNameSubmission CryptoUUIDSubmission -> YesodDB UniWorX AuthResult -submissionAccess cID = do - authId <- lift requireAuthId - submissionId <- either decrypt decrypt cID - Submission{..} <- get404 submissionId - submissionUsers <- map (submissionUserUser . entityVal) <$> selectList [SubmissionUserSubmission ==. submissionId] [] - let auth = authId `elem` submissionUsers || Just authId == submissionRatingBy - return $ case auth of - True -> Authorized - False -> Unauthorized "No access to this submission" - -adminAccess :: Maybe SchoolId -- ^ If @Just@, matched exactly against 'userAdminSchool' - -> YesodDB UniWorX AuthResult -adminAccess school = do - authId <- lift requireAuthId - adrights <- selectList ((UserAdminUser ==. authId) : maybe [] (\s -> [UserAdminSchool ==. s]) school) [] - return $ if (not $ null adrights) - then Authorized - else Unauthorized "No admin access" -- TODO internationalize - -lecturerAccess :: Maybe SchoolId - -> YesodDB UniWorX AuthResult -lecturerAccess school = do - authId <- lift requireAuthId - lecrights <- selectList ((UserLecturerUser ==. authId) : maybe [] (\s -> [UserLecturerSchool ==. s]) school) [] - return $ if (not $ null lecrights) - then Authorized - else Unauthorized "No lecturer access" -- TODO internationalize - -lecturerAccess' :: SchoolId -> YesodDB UniWorX AuthResult -lecturerAccess' = authorizedFor UniqueSchoolLecturer MsgUnauthorizedSchoolLecturer - -courseLecturerAccess :: CourseId -> YesodDB UniWorX AuthResult -courseLecturerAccess = authorizedFor UniqueLecturer MsgUnauthorizedLecturer - ---courseCorrectorAccess :: CourseId -> YesodDB UniWorX AuthResult ---courseCorrectorAccess = authorizedFor UniqueCorrector MsgUnauthorizedCorrector --- TODO: Correctors are no longer unit, could be ByTutorial and also by ByProportion - -courseParticipantAccess :: CourseId -> YesodDB UniWorX AuthResult -courseParticipantAccess = authorizedFor UniqueParticipant MsgUnauthorizedParticipant - -authorizedFor :: ( PersistEntityBackend record ~ BaseBackend backend - , PersistEntity record, PersistUniqueRead backend - , YesodAuth master, RenderMessage master msg - ) - => (AuthId master -> t -> Unique record) -> msg -> t -> ReaderT backend (HandlerT master IO) AuthResult -authorizedFor authType msg courseId = do - authId <- lift requireAuthId - access <- getBy $ authType authId courseId - case access of - (Just _) -> return Authorized - Nothing -> unauthorizedI msg - -isAuthorizedDB' :: Route UniWorX -> Bool -> YesodDB UniWorX Bool -isAuthorizedDB' route isWrite = (== Authorized) <$> isAuthorizedDB route isWrite - -isAuthorized' :: Route UniWorX -> Bool -> Handler Bool -isAuthorized' route isWrite = runDB $ isAuthorizedDB' route isWrite --} - -- Define breadcrumbs. instance YesodBreadcrumbs UniWorX where From 8c4b2d62d34af2a6675e0237643e1073ca02f5ae Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Thu, 7 Jun 2018 11:48:14 +0200 Subject: [PATCH 069/559] got rid of comments and added header for termsList --- messages/de.msg | 3 ++- src/Foundation.hs | 2 ++ templates/default-layout.hamlet | 2 -- templates/terms.hamlet | 4 ---- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/messages/de.msg b/messages/de.msg index a4e8637fa..d5403dc7f 100644 --- a/messages/de.msg +++ b/messages/de.msg @@ -49,4 +49,5 @@ SubmissionAlreadyExistsFor user@Text: #{user} hat bereits eine Abgabe zu diesem EMailUnknown email@Text: E-Mail #{email} gehört zu keinem bekannten Benutzer. NotAParticipant user@Text tid@TermIdentifier csh@Text: #{user} ist nicht im Kurs #{termToText tid}-#{csh} angemeldet. -HomeHeading: Startseite \ No newline at end of file +HomeHeading: Startseite +TermsHeading: Semesterübersicht diff --git a/src/Foundation.hs b/src/Foundation.hs index 340285e0f..13752052b 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -690,6 +690,8 @@ pageActions _ = [] pageHeading :: Route UniWorX -> Maybe Widget pageHeading HomeR = Just [whamlet|_{MsgHomeHeading}|] +pageHeading TermShowR + = Just [whamlet|_{MsgTermsHeading}|] pageHeading _ = Nothing diff --git a/templates/default-layout.hamlet b/templates/default-layout.hamlet index 165096dfd..52b1e584b 100644 --- a/templates/default-layout.hamlet +++ b/templates/default-layout.hamlet @@ -13,11 +13,9 @@ $with status2 <- bool status "info" (status == "")
#{msg} - $maybe headline <- contentHeadline

^{headline} - ^{pageactionprime} diff --git a/templates/terms.hamlet b/templates/terms.hamlet index df30fd90f..2d2943787 100644 --- a/templates/terms.hamlet +++ b/templates/terms.hamlet @@ -1,6 +1,2 @@
-

Semesterübersicht - - - ^{table} From 48888f3706d5d7412646fe8d652b901cbde0a058 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Thu, 7 Jun 2018 12:03:11 +0200 Subject: [PATCH 070/559] got rid of example file in multifileuplaod --- templates/multiFileField.hamlet | 7 ------- 1 file changed, 7 deletions(-) diff --git a/templates/multiFileField.hamlet b/templates/multiFileField.hamlet index 62edf90ec..6a0b9c731 100644 --- a/templates/multiFileField.hamlet +++ b/templates/multiFileField.hamlet @@ -6,13 +6,6 @@ $forall FileUploadInfo{..} <- fileInfos

+ $# if allowed to register
Do 15.04.18 Do 18.04.18 NotGraded -
- Neues Übungsblatt anlegen

Übungsgruppen diff --git a/templates/widgets/asidenav.hamlet b/templates/widgets/asidenav.hamlet index cd00430be..2ab729e25 100644 --- a/templates/widgets/asidenav.hamlet +++ b/templates/widgets/asidenav.hamlet @@ -17,7 +17,8 @@ $newline never WiSe 17/18
+$newline never +
$maybe sortableP <- pSortable $with toSortable <- toSortable sortableP - $forall OneColonnade{..} <- getColonnade dbtColonnade - ^{widgetFromCell th $ withSortLinks $ toSortable oneColonnadeHead} + + $forall OneColonnade{..} <- getColonnade dbtColonnade + + + ^{widgetFromCell th $ withSortLinks $ toSortable oneColonnadeHead} $nothing $forall row <- rows - + $forall OneColonnade{..} <- getColonnade dbtColonnade + + ^{widgetFromCell td $ oneColonnadeEncode row} diff --git a/templates/table/colonnade.lucius b/templates/table/colonnade.lucius index c775325d9..63a42bb84 100644 --- a/templates/table/colonnade.lucius +++ b/templates/table/colonnade.lucius @@ -1,16 +1,76 @@ -table th { - position: relative; - padding-right: 20px; - &.sortable { - cursor: pointer; - } +.table { + margin: 21px 0; + width: 100%; +} - a { - font-weight: 800; +.table--striped { + + .table__row:not(.no-stripe):nth-child(even) { + background-color: rgba(0, 0, 0, 0.07); } } +.table--hover { + + .table__row:not(.no-hover):not(.table__row--head):hover { + background-color: rgba(0, 0, 0, 0.15); + } +} + +/* TABLE DESIGN */ +.table__row { + + /* TODO: move outside of table__row as soon as tds and ths get their own class */ + /* .table__td, .table__th { */ + td, th { + padding-top: 14px; + padding-bottom: 10px; + padding-left: 10px; + padding-right: 10px; + vertical-align: top; + max-width: 300px; + } + + /* .table__td { */ + td { + font-size: 16px; + color: #808080; + line-height: 1.4; + } + + &.table__row--head { + background-color: var(--color-dark); + } + + /* .table__th { */ + th { + position: relative; + font-size: 16px; + color: #fff; + line-height: 1.4; + padding-top: 15px; + padding-bottom: 10px; + font-weight: bold; + text-align: left; + } +} + +.table__td-content { + max-height: 100px; + overflow-y: auto; +} + +.table__th-link { + color: white; + font-weight: bold; + + &:hover { + background-color: var(--color-light); + } +} + +/* SORTABLE */ table th.sorted-asc, table th.sorted-desc { color: var(--color-light); diff --git a/templates/table/layout.lucius b/templates/table/layout.lucius index 5b9232406..6dcbed18b 100644 --- a/templates/table/layout.lucius +++ b/templates/table/layout.lucius @@ -1,3 +1,4 @@ +/* PAGINATION */ .pagination { margin-top: 20px; text-align: center; @@ -36,3 +37,8 @@ } } } + +/* SCROLLTABLE */ +.scrolltable { + overflow: auto; +} diff --git a/templates/table/sortable-header.hamlet b/templates/table/sortable-header.hamlet index 68a7806a4..188c3a779 100644 --- a/templates/table/sortable-header.hamlet +++ b/templates/table/sortable-header.hamlet @@ -1,10 +1,10 @@ $maybe flag <- sortableKey $case directions $of [SortAsc] - "-desc")}> + "-desc")}> ^{cellContents} $of _ - "-asc")}> + "-asc")}> ^{cellContents} $nothing ^{cellContents} From c54495fe589778de21fab65e15312a1359a29822 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 10 Jun 2018 19:36:39 +0200 Subject: [PATCH 097/559] renamed a few pageactions --- src/Foundation.hs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Foundation.hs b/src/Foundation.hs index 2a968200c..be28d2245 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -574,7 +574,7 @@ pageActions (CourseR tid csh CShowR) = ] pageActions (CourseR tid csh SheetListR) = [ PageActionPrime $ MenuItem - { menuItemLabel = "Neues Übungsblatt" + { menuItemLabel = "Neues Übungsblatt anlegen" , menuItemIcon = Nothing , menuItemRoute = CourseR tid csh SheetNewR , menuItemAccessCallback' = return True @@ -596,7 +596,7 @@ pageActions (CSheetR tid csh shn SShowR) = ] pageActions TermShowR = [ PageActionPrime $ MenuItem - { menuItemLabel = "Neues Semester" + { menuItemLabel = "Neues Semester anlegen" , menuItemIcon = Nothing , menuItemRoute = TermEditR , menuItemAccessCallback' = return True @@ -604,7 +604,7 @@ pageActions TermShowR = ] pageActions (TermCourseListR _) = [ PageActionPrime $ MenuItem - { menuItemLabel = "Neuer Kurs" + { menuItemLabel = "Neuen Kurs anlegen" , menuItemIcon = Just "book" , menuItemRoute = CourseNewR , menuItemAccessCallback' = return True From b5051306210909e82b921008133ba1bd8f47f7b3 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 10 Jun 2018 20:17:14 +0200 Subject: [PATCH 098/559] fixed styling of sort-buttons in sortable tables --- templates/table/colonnade.lucius | 71 ++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/templates/table/colonnade.lucius b/templates/table/colonnade.lucius index 63a42bb84..1da9b5e60 100644 --- a/templates/table/colonnade.lucius +++ b/templates/table/colonnade.lucius @@ -49,8 +49,8 @@ font-size: 16px; color: #fff; line-height: 1.4; - padding-top: 15px; - padding-bottom: 10px; + padding-top: 20px; + padding-bottom: 15px; font-weight: bold; text-align: left; } @@ -66,40 +66,49 @@ font-weight: bold; &:hover { - background-color: var(--color-light); + color: inherit; } } /* SORTABLE */ -table th.sorted-asc, -table th.sorted-desc { - color: var(--color-light); -} +.table { -table th.sortable::after, -table th.sortable::before { - content: ''; - position: absolute; - right: 0; - width: 0; - height: 0; - transform: translateY(-100%); - border-left: 8px solid transparent; - border-right: 8px solid transparent; -} + /* TODO: move outside of table as soon as tds and ths get their own class */ + th.sortable { + position: relative; + } -table th.sortable::before { - top: 21px; - border-top: 8px solid rgba(0, 0, 0, 0.1); -} -table th.sortable::after { - top: 9px; - border-bottom: 8px solid rgba(0, 0, 0, 0.1); -} -table th.sorted-asc::before { - border-top: 8px solid var(--color-light); -} + th.sortable::after, + th.sortable::before { + content: ''; + position: absolute; + right: 0; + width: 0; + height: 0; + left: 50%; + transform: translate(-50%, -100%); + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid rgba(255, 255, 255, 0.4); + } -table th.sorted-desc::after { - border-bottom: 8px solid var(--color-light); + th.sortable::before { + /* magic numbers to move arrow back in the right position after flipping it. + this allows us to use the same border for the up and the down arrow */ + bottom: -3px; + transform: translateX(-8px) scale(1, -1); + transform-origin: top; + } + + th.sortable::after { + top: 15px; + } + + th.sortable:hover::before, + th.sorted-asc:hover::after, + th.sorted-asc::before, + th.sorted-desc:hover::after, + th.sorted-desc::after { + border-bottom-color: white; + } } From a1b6599a29beb713dc283d60deafa9cef265b400 Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 10 Jun 2018 20:19:00 +0200 Subject: [PATCH 099/559] fixed deprecated alerts in backend. we need a template for alerts --- src/Handler/Course.hs | 9 +++++---- src/Handler/Sheet.hs | 19 ++++++++++--------- src/Handler/Term.hs | 9 +++++---- templates/default-layout.hamlet | 4 ++-- templates/home.hamlet | 3 ++- templates/standalone/alerts.julius | 7 ++++++- templates/standalone/alerts.lucius | 1 + 7 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/Handler/Course.hs b/src/Handler/Course.hs index 776d4e03b..a597a504a 100644 --- a/src/Handler/Course.hs +++ b/src/Handler/Course.hs @@ -324,10 +324,11 @@ newCourseForm template = identForm FIDcourse $ \html -> do (FormFailure errorMsgs, [whamlet|
-

Fehler: -
    - $forall errmsg <- errorMsgs -
  • #{errmsg} +
    +

    Fehler: +
      + $forall errmsg <- errorMsgs +
    • #{errmsg} ^{widget} |] ) diff --git a/src/Handler/Sheet.hs b/src/Handler/Sheet.hs index 57e1caa16..997703a1e 100644 --- a/src/Handler/Sheet.hs +++ b/src/Handler/Sheet.hs @@ -12,7 +12,7 @@ module Handler.Sheet where -import Import +import Import import System.FilePath (takeFileName) import Handler.Utils @@ -21,10 +21,10 @@ import Handler.Utils.Zip -- import Data.Time import qualified Data.Text as T -- import Data.Function ((&)) --- +-- import Colonnade hiding (fromMaybe, singleton) import Yesod.Colonnade --- +-- import qualified Data.UUID.Cryptographic as UUID import qualified Data.Conduit.List as C @@ -44,7 +44,7 @@ instance Eq (Unique Sheet) where {- * Implement Handlers - * Implement Breadcrumbs in Foundation + * Implement Breadcrumbs in Foundation * Implement Access in Foundation -} @@ -76,7 +76,7 @@ makeSheetForm msId template = identForm FIDsheet $ \html -> do E.&&. sheetFile E.^. SheetFileType E.==. E.val fType return (file E.^. FileId) | otherwise = return Set.empty - + (result, widget) <- flip (renderAForm FormStandard) html $ SheetForm <$> areq textField (fsb "Name") (sfName <$> template) <*> aopt htmlField (fsb "Hinweise für Teilnehmer") (sfDescription <$> template) @@ -100,10 +100,11 @@ makeSheetForm msId template = identForm FIDsheet $ \html -> do (FormFailure errorMsgs, [whamlet|
      -

      Fehler: -
        - $forall errmsg <- errorMsgs -
      • #{errmsg} +
        +

        Fehler: +
          + $forall errmsg <- errorMsgs +
        • #{errmsg} ^{widget} |] ) diff --git a/src/Handler/Term.hs b/src/Handler/Term.hs index a6942d85f..bc8938ef3 100644 --- a/src/Handler/Term.hs +++ b/src/Handler/Term.hs @@ -149,10 +149,11 @@ newTermForm template html = do (FormFailure errorMsgs, [whamlet|
          -

          Fehler: -
            - $forall errmsg <- errorMsgs -
          • #{errmsg} +
            +

            Fehler: +
              + $forall errmsg <- errorMsgs +
            • #{errmsg} ^{widget} |] ) diff --git a/templates/default-layout.hamlet b/templates/default-layout.hamlet index 39586cb46..5f81f4e07 100644 --- a/templates/default-layout.hamlet +++ b/templates/default-layout.hamlet @@ -23,8 +23,8 @@ $forall (status, msg) <- mmsgs $with status2 <- bool status "info" (status == "")
              -
              #{msg} -
              _{MsgCloseAlert} +
              + #{msg} diff --git a/templates/home.hamlet b/templates/home.hamlet index 69435db5c..f57e6d570 100644 --- a/templates/home.hamlet +++ b/templates/home.hamlet @@ -6,7 +6,8 @@ Die Reimplementierung von UniWorX ist noch nicht abgeschlossen. -

              Das System ist noch nicht produktiv einsetzbar +

              +
              Das System ist noch nicht produktiv einsetzbar
              diff --git a/templates/standalone/alerts.julius b/templates/standalone/alerts.julius index 253db2674..f32962cb7 100644 --- a/templates/standalone/alerts.julius +++ b/templates/standalone/alerts.julius @@ -4,9 +4,14 @@ window.utils = window.utils || {}; window.utils.alert = function(alertEl) { - alertEl.querySelector('.alert__close').addEventListener('click', function(event) { + var closeEl = document.createElement('DIV'); + closeEl.classList.add('alert__close'); + // TODO: fix this. How to request translation in *.julius .files? + closeEl.innerText = "_{MsgCloseAlert}"; + closeEl.addEventListener('click', function(event) { alertEl.classList.add('alert--invisible'); }); + alertEl.appendChild(closeEl); } })(); diff --git a/templates/standalone/alerts.lucius b/templates/standalone/alerts.lucius index e5ddc7b1f..69869d997 100644 --- a/templates/standalone/alerts.lucius +++ b/templates/standalone/alerts.lucius @@ -56,6 +56,7 @@ } } +.alert-danger, .alert-error { border-color: var(--color-error); background-color: #fff5f7; From 0188673e1f16f15e37f97e197a3c5eee3cd93b0c Mon Sep 17 00:00:00 2001 From: Felix Hamann Date: Sun, 10 Jun 2018 20:51:41 +0200 Subject: [PATCH 100/559] added datepicker globally --- src/Foundation.hs | 1 + templates/home.hamlet | 10 +++---- templates/home.julius | 19 -------------- templates/standalone/datepicker.hamlet | 1 + templates/standalone/datepicker.julius | 36 ++++++++++++++++++++++++++ templates/widgets/form.hamlet | 1 + 6 files changed, 44 insertions(+), 24 deletions(-) delete mode 100644 templates/home.julius create mode 100644 templates/standalone/datepicker.hamlet create mode 100644 templates/standalone/datepicker.julius diff --git a/src/Foundation.hs b/src/Foundation.hs index be28d2245..ad8f9b5b8 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -482,6 +482,7 @@ instance Yesod UniWorX where $(widgetFile "standalone/inputs") $(widgetFile "standalone/tabber") $(widgetFile "standalone/alerts") + $(widgetFile "standalone/datepicker") withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet") -- The page to be redirected to when authentication is required. diff --git a/templates/home.hamlet b/templates/home.hamlet index f57e6d570..b827a56dd 100644 --- a/templates/home.hamlet +++ b/templates/home.hamlet @@ -30,11 +30,11 @@

              Date picker - - - - - + + + + +
              diff --git a/templates/home.julius b/templates/home.julius deleted file mode 100644 index c62129e2c..000000000 --- a/templates/home.julius +++ /dev/null @@ -1,19 +0,0 @@ -document.addEventListener('DOMContentLoaded', function() { - 'use strict'; - - var config = { - enableTime: true, - altInput: true, - altFormat: "j. F Y, H:i", - dateFormat: "Y-m-d H:i", - time_24hr: true - }; - - flatpickr('#datetime-form1 [type="date"]', { - altFormat: "j. F Y", dateFormat: "Y-m-d", altInput: true - }); - flatpickr('#datetime-form1 [type="time"]', { - enableTime: true, noCalendar: true, altFormat: "H:i", dateFormat: "H:i", altInput: true, time_24hr: true - }); - flatpickr('#datetime-form2 input', config); -}); diff --git a/templates/standalone/datepicker.hamlet b/templates/standalone/datepicker.hamlet new file mode 100644 index 000000000..4ec550e09 --- /dev/null +++ b/templates/standalone/datepicker.hamlet @@ -0,0 +1 @@ + diff --git a/templates/standalone/datepicker.julius b/templates/standalone/datepicker.julius new file mode 100644 index 000000000..f056b5062 --- /dev/null +++ b/templates/standalone/datepicker.julius @@ -0,0 +1,36 @@ +document.addEventListener('DOMContentLoaded', function() { + "use strict"; + + var config = { + dtLocal: { + enableTime: true, + altInput: true, + altFormat: "j. F Y, H:i", + dateFormat: "Y-m-d H:i", + time_24hr: true + }, + d: { + altFormat: "j. F Y", + dateFormat: "Y-m-d", + altInput: true + }, + t: { + enableTime: true, + noCalendar: true, + altFormat: "H:i", + dateFormat: "H:i", + altInput: true, + time_24hr: true + } + }; + + Array.from(document.querySelectorAll('input[type="date"]')).forEach(function(el) { + flatpickr(el, config.d); + }); + Array.from(document.querySelectorAll('input[type="time"]')).forEach(function(el) { + flatpickr(el, config.t); + }); + Array.from(document.querySelectorAll('input[type="datetime-local"]')).forEach(function(el) { + flatpickr(el, config.dtLocal); + }); +}); diff --git a/templates/widgets/form.hamlet b/templates/widgets/form.hamlet index 91c015dd9..fdc9d5fb2 100644 --- a/templates/widgets/form.hamlet +++ b/templates/widgets/form.hamlet @@ -6,4 +6,5 @@ $case formLayout
              $if not (Blaze.null $ fvLabel view)