Merge branch 'master' of gitlab.cip.ifi.lmu.de:jost/UniWorX
Refactoring for FileTypes complete
This commit is contained in:
commit
d5064151ee
12
config/submission-blacklist
Normal file
12
config/submission-blacklist
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
$# Syntax:
|
||||||
|
$# - Leere zeilen werden ignoriert
|
||||||
|
$# - Zeilen, die mit '$#' beginnen, werden ignoriert
|
||||||
|
$# - Verbleibende Zeilen werden jeweils als `Glob`-Pattern kompiliert
|
||||||
|
|
||||||
|
$# Ignoriere rekursiv alle Ordner __MACOSX und ihren Inhalt
|
||||||
|
**/__MACOSX
|
||||||
|
**/__MACOSX/*
|
||||||
|
**/__MACOSX/**/*
|
||||||
|
|
||||||
|
$# Ignoriere rekursiv alle Dateien .DS_Store
|
||||||
|
**/.DS_Store
|
||||||
@ -174,6 +174,7 @@ UpdatedAssignedCorrectorsAuto num@Int64: #{display num} Abgaben wurden unter den
|
|||||||
CouldNotAssignCorrectorsAuto num@Int64: #{display num} Abgaben konnten nicht automatisch zugewiesen werden:
|
CouldNotAssignCorrectorsAuto num@Int64: #{display num} Abgaben konnten nicht automatisch zugewiesen werden:
|
||||||
|
|
||||||
CorrectionsUploaded num@Int64: #{display num} Korrekturen wurden gespeichert:
|
CorrectionsUploaded num@Int64: #{display num} Korrekturen wurden gespeichert:
|
||||||
|
NoCorrectionsUploaded: In der hochgeladenen Datei wurden keine Korrekturen gefunden.
|
||||||
|
|
||||||
RatingBy: Korrigiert von
|
RatingBy: Korrigiert von
|
||||||
AchievedBonusPoints: Erreichte Bonuspunkte
|
AchievedBonusPoints: Erreichte Bonuspunkte
|
||||||
@ -221,3 +222,6 @@ AmbiguousUTCTime: Der angegebene Zeitpunkt lässt sich nicht eindeutig zu UTC ko
|
|||||||
LastEdits: Letzte Änderungen
|
LastEdits: Letzte Änderungen
|
||||||
EditedBy name@Text time@Text: Durch #{name} um #{time}
|
EditedBy name@Text time@Text: Durch #{name} um #{time}
|
||||||
LastEdit: Letzte Änderung
|
LastEdit: Letzte Änderung
|
||||||
|
|
||||||
|
SubmissionFilesIgnored: Es wurden Dateien in der hochgeladenen Abgabe ignoriert:
|
||||||
|
SubmissionDoesNotExist smid@CryptoFileNameSubmission: Es existiert keine Abgabe mit Nummer #{toPathPiece smid}.
|
||||||
@ -86,6 +86,8 @@ dependencies:
|
|||||||
- tz
|
- tz
|
||||||
- system-locale
|
- system-locale
|
||||||
- th-lift-instances
|
- th-lift-instances
|
||||||
|
- gitrev
|
||||||
|
- Glob
|
||||||
|
|
||||||
# The library contains all of our application code. The executable
|
# The library contains all of our application code. The executable
|
||||||
# defined below is just a thin wrapper.
|
# defined below is just a thin wrapper.
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
{-# LANGUAGE TypeOperators #-}
|
{-# LANGUAGE TypeOperators #-}
|
||||||
{-# LANGUAGE ViewPatterns #-}
|
{-# LANGUAGE ViewPatterns #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
{-# LANGUAGE MultiWayIf #-}
|
||||||
|
|
||||||
module Handler.Corrections where
|
module Handler.Corrections where
|
||||||
|
|
||||||
@ -195,7 +196,7 @@ data ActionCorrectionsData = CorrDownloadData
|
|||||||
correctionsR :: _ -> _ -> _ -> Map ActionCorrections (MForm (HandlerT UniWorX IO) (FormResult ActionCorrectionsData, Maybe Widget)) -> Handler TypedContent
|
correctionsR :: _ -> _ -> _ -> Map ActionCorrections (MForm (HandlerT UniWorX IO) (FormResult ActionCorrectionsData, Maybe Widget)) -> Handler TypedContent
|
||||||
correctionsR whereClause (formColonnade -> displayColumns) psValidator actions = do
|
correctionsR whereClause (formColonnade -> displayColumns) psValidator actions = do
|
||||||
tableForm <- makeCorrectionsTable whereClause displayColumns psValidator
|
tableForm <- makeCorrectionsTable whereClause displayColumns psValidator
|
||||||
((actionRes, table), tableEncoding) <- runFormPost . identForm FIDcorrectorTable $ \csrf -> do
|
((actionRes, table), tableEncoding) <- runFormPost $ \csrf -> do
|
||||||
((fmap (Map.keysSet . Map.filter id . getDBFormResult (const False)) -> selectionRes), table) <- tableForm csrf
|
((fmap (Map.keysSet . Map.filter id . getDBFormResult (const False)) -> selectionRes), table) <- tableForm csrf
|
||||||
(actionRes, action) <- multiAction actions
|
(actionRes, action) <- multiAction actions
|
||||||
return ((,) <$> actionRes <*> selectionRes, table <> action)
|
return ((,) <$> actionRes <*> selectionRes, table <> action)
|
||||||
@ -215,7 +216,7 @@ correctionsR whereClause (formColonnade -> displayColumns) psValidator actions =
|
|||||||
when (not $ null alreadyAssigned) $ do
|
when (not $ null alreadyAssigned) $ do
|
||||||
mr <- (toHtml . ) <$> getMessageRender
|
mr <- (toHtml . ) <$> getMessageRender
|
||||||
alreadyAssigned' <- forM alreadyAssigned $ \Entity{..} -> (, entityVal) <$> (encrypt entityKey :: DB CryptoFileNameSubmission)
|
alreadyAssigned' <- forM alreadyAssigned $ \Entity{..} -> (, entityVal) <$> (encrypt entityKey :: DB CryptoFileNameSubmission)
|
||||||
addMessage "warn" =<< withUrlRenderer ($(ihamletFile "templates/messages/submissionsAlreadyAssigned.hamlet") mr)
|
addMessage "warning" =<< withUrlRenderer ($(ihamletFile "templates/messages/submissionsAlreadyAssigned.hamlet") mr)
|
||||||
let unassigned = Set.fromList subs `Set.difference` Set.fromList (entityKey <$> alreadyAssigned)
|
let unassigned = Set.fromList subs `Set.difference` Set.fromList (entityKey <$> alreadyAssigned)
|
||||||
when (not $ null unassigned) $ do
|
when (not $ null unassigned) $ do
|
||||||
num <- updateWhereCount [SubmissionId <-. Set.toList unassigned] [SubmissionRatingBy =. Just uid]
|
num <- updateWhereCount [SubmissionId <-. Set.toList unassigned] [SubmissionRatingBy =. Just uid]
|
||||||
@ -238,7 +239,7 @@ correctionsR whereClause (formColonnade -> displayColumns) psValidator actions =
|
|||||||
when (not $ null alreadyAssigned) $ do
|
when (not $ null alreadyAssigned) $ do
|
||||||
mr <- (toHtml . ) <$> getMessageRender
|
mr <- (toHtml . ) <$> getMessageRender
|
||||||
alreadyAssigned' <- forM alreadyAssigned $ \Entity{..} -> (, entityVal) <$> (encrypt entityKey :: DB CryptoFileNameSubmission)
|
alreadyAssigned' <- forM alreadyAssigned $ \Entity{..} -> (, entityVal) <$> (encrypt entityKey :: DB CryptoFileNameSubmission)
|
||||||
addMessage "warn" =<< withUrlRenderer ($(ihamletFile "templates/messages/submissionsAlreadyAssigned.hamlet") mr)
|
addMessage "warning" =<< withUrlRenderer ($(ihamletFile "templates/messages/submissionsAlreadyAssigned.hamlet") mr)
|
||||||
let unassigned = Set.fromList subs `Set.difference` Set.fromList (entityKey <$> alreadyAssigned)
|
let unassigned = Set.fromList subs `Set.difference` Set.fromList (entityKey <$> alreadyAssigned)
|
||||||
when (not $ null unassigned) $ do
|
when (not $ null unassigned) $ do
|
||||||
(assigned, unassigned) <- assignSubmissions shid (Just unassigned)
|
(assigned, unassigned) <- assignSubmissions shid (Just unassigned)
|
||||||
@ -247,7 +248,7 @@ correctionsR whereClause (formColonnade -> displayColumns) psValidator actions =
|
|||||||
when (not $ null unassigned) $ do
|
when (not $ null unassigned) $ do
|
||||||
mr <- (toHtml . ) <$> getMessageRender
|
mr <- (toHtml . ) <$> getMessageRender
|
||||||
unassigned' <- forM (Set.toList unassigned) $ \sid -> (encrypt sid :: DB CryptoFileNameSubmission)
|
unassigned' <- forM (Set.toList unassigned) $ \sid -> (encrypt sid :: DB CryptoFileNameSubmission)
|
||||||
addMessage "warn" =<< withUrlRenderer ($(ihamletFile "templates/messages/submissionsNotAssignedAuto.hamlet") mr)
|
addMessage "warning" =<< withUrlRenderer ($(ihamletFile "templates/messages/submissionsNotAssignedAuto.hamlet") mr)
|
||||||
redirect currentRoute
|
redirect currentRoute
|
||||||
|
|
||||||
fmap toTypedContent . defaultLayout $ do
|
fmap toTypedContent . defaultLayout $ do
|
||||||
@ -403,7 +404,7 @@ postCorrectionR tid csh shn cid = do
|
|||||||
FormSuccess fileSource -> do
|
FormSuccess fileSource -> do
|
||||||
uid <- requireAuthId
|
uid <- requireAuthId
|
||||||
|
|
||||||
runDB . runConduit $ transPipe lift fileSource .| extractRatings .| sinkSubmission uid (Right sub) True
|
runDB . runConduit $ transPipe lift fileSource .| extractRatingsMsg .| sinkSubmission uid (Right sub) True
|
||||||
|
|
||||||
addMessageI "success" MsgRatingFilesUpdated
|
addMessageI "success" MsgRatingFilesUpdated
|
||||||
redirect $ CSubmissionR tid csh shn cid CorrectionR
|
redirect $ CSubmissionR tid csh shn cid CorrectionR
|
||||||
@ -438,10 +439,14 @@ postCorrectionsUploadR = do
|
|||||||
FormFailure errs -> mapM_ (addMessage "error" . toHtml) errs
|
FormFailure errs -> mapM_ (addMessage "error" . toHtml) errs
|
||||||
FormSuccess files -> do
|
FormSuccess files -> do
|
||||||
uid <- requireAuthId
|
uid <- requireAuthId
|
||||||
subs <- runDB . runConduit $ transPipe lift files .| extractRatings .| sinkMultiSubmission uid True
|
subs <- runDB . runConduit $ transPipe lift files .| extractRatingsMsg .| sinkMultiSubmission uid True
|
||||||
subs' <- traverse encrypt $ Set.toList subs :: Handler [CryptoFileNameSubmission]
|
if
|
||||||
mr <- (toHtml .) <$> getMessageRender
|
| null subs -> addMessageI "warning" MsgNoCorrectionsUploaded
|
||||||
addMessage "success" =<< withUrlRenderer ($(ihamletFile "templates/messages/correctionsUploaded.hamlet") mr)
|
| otherwise -> do
|
||||||
|
subs' <- traverse encrypt $ Set.toList subs :: Handler [CryptoFileNameSubmission]
|
||||||
|
mr <- (toHtml .) <$> getMessageRender
|
||||||
|
addMessage "success" =<< withUrlRenderer ($(ihamletFile "templates/messages/correctionsUploaded.hamlet") mr)
|
||||||
|
|
||||||
|
|
||||||
defaultLayout $ do
|
defaultLayout $ do
|
||||||
$(widgetFile "corrections-upload")
|
$(widgetFile "corrections-upload")
|
||||||
|
|||||||
@ -29,6 +29,8 @@ import qualified Database.Esqueleto as E
|
|||||||
|
|
||||||
import Text.Shakespeare.Text
|
import Text.Shakespeare.Text
|
||||||
|
|
||||||
|
import Development.GitRev
|
||||||
|
|
||||||
-- import qualified Data.UUID.Cryptographic as UUID
|
-- import qualified Data.UUID.Cryptographic as UUID
|
||||||
|
|
||||||
|
|
||||||
@ -196,9 +198,13 @@ homeUser uid = do
|
|||||||
$(widgetFile "dsgvDisclaimer")
|
$(widgetFile "dsgvDisclaimer")
|
||||||
|
|
||||||
|
|
||||||
getVersionR :: Handler Html
|
getVersionR :: Handler TypedContent
|
||||||
getVersionR = do
|
getVersionR = selectRep $ do
|
||||||
let features = $(widgetFile "featureList")
|
provideRep . defaultLayout $ do
|
||||||
changeLog <- withUrlRenderer $(textFile "ChangeLog.md")
|
let features = $(widgetFile "featureList")
|
||||||
defaultLayout $ do
|
gitInfo :: Text
|
||||||
$(widgetFile "versionHistory")
|
gitInfo = $gitDescribe <> " (" <> $gitCommitDate <> ")"
|
||||||
|
changeLog <- withUrlRenderer $(textFile "ChangeLog.md")
|
||||||
|
$(widgetFile "versionHistory")
|
||||||
|
provideRep $
|
||||||
|
return ($gitDescribe :: Text)
|
||||||
|
|||||||
@ -492,7 +492,7 @@ correctorForm shid = do
|
|||||||
(defaultLoads', currentLoads') <- lift . runDB $ (,) <$> defaultLoads shid <*> currentLoads
|
(defaultLoads', currentLoads') <- lift . runDB $ (,) <$> defaultLoads shid <*> currentLoads
|
||||||
loads' <- fmap (Map.fromList [(uid, mempty) | uid <- formCIDs] `Map.union`) $ if
|
loads' <- fmap (Map.fromList [(uid, mempty) | uid <- formCIDs] `Map.union`) $ if
|
||||||
| Map.null currentLoads'
|
| Map.null currentLoads'
|
||||||
, null formCIDs -> defaultLoads' <$ when (not $ Map.null defaultLoads') (addMessageI "warn" MsgCorrectorsDefaulted)
|
, null formCIDs -> defaultLoads' <$ when (not $ Map.null defaultLoads') (addMessageI "warning" MsgCorrectorsDefaulted)
|
||||||
| otherwise -> return $ Map.fromList (map (, mempty) formCIDs) `Map.union` currentLoads'
|
| otherwise -> return $ Map.fromList (map (, mempty) formCIDs) `Map.union` currentLoads'
|
||||||
|
|
||||||
deletions <- lift $ foldM (\dels uid -> maybe (Set.insert uid dels) (const dels) <$> guardNonDeleted uid) Set.empty (Map.keys loads')
|
deletions <- lift $ foldM (\dels uid -> maybe (Set.insert uid dels) (const dels) <$> guardNonDeleted uid) Set.empty (Map.keys loads')
|
||||||
|
|||||||
@ -220,7 +220,7 @@ submissionHelper tid csh shn (SubmissionMode mcid) = do
|
|||||||
(Nothing, Just smid) -- no new files, existing submission partners updated
|
(Nothing, Just smid) -- no new files, existing submission partners updated
|
||||||
-> return smid
|
-> return smid
|
||||||
(Just files, _) -- new files
|
(Just files, _) -- new files
|
||||||
-> runConduit $ transPipe lift files .| extractRatings .| sinkSubmission uid (maybe (Left shid) Right msmid) False
|
-> runConduit $ transPipe lift files .| extractRatingsMsg .| sinkSubmission uid (maybe (Left shid) Right msmid) False
|
||||||
_ -> error "Impossible, because of definition of `makeSubmissionForm`"
|
_ -> error "Impossible, because of definition of `makeSubmissionForm`"
|
||||||
-- Determine members of pre-registered group
|
-- Determine members of pre-registered group
|
||||||
groupUids <- fmap (setFromList . map E.unValue) . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup `E.InnerJoin` submissionGroupUser') -> do
|
groupUids <- fmap (setFromList . map E.unValue) . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionGroup `E.InnerJoin` submissionGroupUser') -> do
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import Handler.Utils.Table as Handler.Utils
|
|||||||
import Handler.Utils.Table.Pagination as Handler.Utils
|
import Handler.Utils.Table.Pagination as Handler.Utils
|
||||||
|
|
||||||
import Handler.Utils.Zip as Handler.Utils
|
import Handler.Utils.Zip as Handler.Utils
|
||||||
import Handler.Utils.Rating as Handler.Utils
|
import Handler.Utils.Rating as Handler.Utils hiding (extractRatings)
|
||||||
import Handler.Utils.Submission as Handler.Utils
|
import Handler.Utils.Submission as Handler.Utils
|
||||||
import Handler.Utils.Sheet as Handler.Utils
|
import Handler.Utils.Sheet as Handler.Utils
|
||||||
import Handler.Utils.Templates as Handler.Utils
|
import Handler.Utils.Templates as Handler.Utils
|
||||||
|
|||||||
@ -153,9 +153,11 @@ parseRating :: MonadThrow m => File -> m Rating'
|
|||||||
parseRating File{ fileContent = Just input, .. } = do
|
parseRating File{ fileContent = Just input, .. } = do
|
||||||
inputText <- either (throw . RatingNotUnicode) return $ Text.decodeUtf8' input
|
inputText <- either (throw . RatingNotUnicode) return $ Text.decodeUtf8' input
|
||||||
let
|
let
|
||||||
(headerLines, commentLines) = break (sep `Text.isInfixOf`) $ Text.lines inputText
|
(headerLines', commentLines) = break (sep `Text.isInfixOf`) $ Text.lines inputText
|
||||||
ratingLines = filter (rating `Text.isInfixOf`) headerLines
|
(reverse -> ratingLines, reverse -> headerLines) = break (sep' `Text.isInfixOf`) $ reverse headerLines'
|
||||||
|
ratingLines' = filter (rating `Text.isInfixOf`) ratingLines
|
||||||
sep = "Beginn der Kommentare"
|
sep = "Beginn der Kommentare"
|
||||||
|
sep' = Text.pack $ replicate 40 '='
|
||||||
rating = "Bewertung:"
|
rating = "Bewertung:"
|
||||||
comment' <- case commentLines of
|
comment' <- case commentLines of
|
||||||
(_:commentLines') -> return . Text.strip $ Text.unlines commentLines'
|
(_:commentLines') -> return . Text.strip $ Text.unlines commentLines'
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
module Handler.Utils.Submission
|
module Handler.Utils.Submission
|
||||||
( AssignSubmissionException(..)
|
( AssignSubmissionException(..)
|
||||||
, assignSubmissions
|
, assignSubmissions
|
||||||
|
, submissionBlacklist, filterSubmission, extractRatings, extractRatingsMsg
|
||||||
, submissionFileSource, submissionFileQuery
|
, submissionFileSource, submissionFileQuery
|
||||||
, submissionMultiArchive
|
, submissionMultiArchive
|
||||||
, SubmissionSinkException(..)
|
, SubmissionSinkException(..)
|
||||||
@ -23,12 +24,15 @@ module Handler.Utils.Submission
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Import hiding ((.=), joinPath)
|
import Import hiding ((.=), joinPath)
|
||||||
|
import Yesod.Core.Types (HandlerContents(..), ErrorResponse(..))
|
||||||
|
|
||||||
import Control.Lens
|
import Control.Lens
|
||||||
import Control.Lens.Extras (is)
|
import Control.Lens.Extras (is)
|
||||||
import Utils.Lens
|
import Utils.Lens
|
||||||
|
|
||||||
import Control.Monad.State hiding (forM_, mapM_,foldM)
|
import Control.Monad.State hiding (forM_, mapM_,foldM)
|
||||||
|
import Control.Monad.Writer (MonadWriter(..))
|
||||||
|
import Control.Monad.RWS.Lazy (RWST)
|
||||||
import qualified Control.Monad.Random as Rand
|
import qualified Control.Monad.Random as Rand
|
||||||
|
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
@ -45,16 +49,24 @@ import qualified Data.CaseInsensitive as CI
|
|||||||
import Data.Monoid (Monoid, Any(..))
|
import Data.Monoid (Monoid, Any(..))
|
||||||
import Generics.Deriving.Monoid (memptydefault, mappenddefault)
|
import Generics.Deriving.Monoid (memptydefault, mappenddefault)
|
||||||
|
|
||||||
import Handler.Utils.Rating
|
import Handler.Utils.Rating hiding (extractRatings)
|
||||||
|
import qualified Handler.Utils.Rating as Rating (extractRatings)
|
||||||
import Handler.Utils.Zip
|
import Handler.Utils.Zip
|
||||||
import Handler.Utils.Sheet
|
import Handler.Utils.Sheet
|
||||||
|
import Handler.Utils.Submission.TH
|
||||||
|
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
|
|
||||||
|
import Data.Conduit
|
||||||
import qualified Data.Conduit.List as Conduit
|
import qualified Data.Conduit.List as Conduit
|
||||||
import Data.Conduit.ResumableSink
|
import Data.Conduit.ResumableSink
|
||||||
|
|
||||||
import System.FilePath
|
import System.FilePath
|
||||||
|
import System.FilePath.Glob
|
||||||
|
|
||||||
|
import Text.Hamlet (ihamletFile)
|
||||||
|
|
||||||
|
import qualified Control.Monad.Catch as E (Handler(..))
|
||||||
|
|
||||||
|
|
||||||
data AssignSubmissionException = NoCorrectorsByProportion
|
data AssignSubmissionException = NoCorrectorsByProportion
|
||||||
@ -186,11 +198,46 @@ instance Monoid SubmissionSinkState where
|
|||||||
data SubmissionSinkException = DuplicateFileTitle FilePath
|
data SubmissionSinkException = DuplicateFileTitle FilePath
|
||||||
| DuplicateRating
|
| DuplicateRating
|
||||||
| RatingWithoutUpdate
|
| RatingWithoutUpdate
|
||||||
| ForeignRating
|
| ForeignRating CryptoFileNameSubmission
|
||||||
deriving (Typeable, Show)
|
deriving (Typeable, Show)
|
||||||
|
|
||||||
instance Exception SubmissionSinkException
|
instance Exception SubmissionSinkException
|
||||||
|
|
||||||
|
submissionBlacklist :: [Pattern]
|
||||||
|
submissionBlacklist = $(patternFile compDefault "config/submission-blacklist")
|
||||||
|
|
||||||
|
filterSubmission :: MonadLogger m => ConduitM File File m (Set FilePath)
|
||||||
|
-- ^ Removes all `File`s matching `submissionBlacklist`, returning their `fileTitle`s
|
||||||
|
filterSubmission = do
|
||||||
|
$logDebugS "filterSubmission" $ tshow submissionBlacklist
|
||||||
|
execWriterLC . awaitForever $ \case
|
||||||
|
File{fileTitle}
|
||||||
|
| any (`match'` fileTitle) submissionBlacklist -> tell $ Set.singleton fileTitle
|
||||||
|
file -> yield file
|
||||||
|
where
|
||||||
|
match' = matchWith $ matchDefault
|
||||||
|
{ matchDotsImplicitly = True -- Special treatment for . makes no sense since we're multiplatform
|
||||||
|
}
|
||||||
|
|
||||||
|
extractRatings :: ( MonadHandler m
|
||||||
|
, HandlerSite m ~ UniWorX
|
||||||
|
, MonadCatch m
|
||||||
|
, MonadLogger m
|
||||||
|
) => ConduitM File SubmissionContent m (Set FilePath)
|
||||||
|
extractRatings = filterSubmission `fuseUpstream` Rating.extractRatings
|
||||||
|
|
||||||
|
extractRatingsMsg :: ( MonadHandler m
|
||||||
|
, HandlerSite m ~ UniWorX
|
||||||
|
, MonadCatch m
|
||||||
|
, MonadLogger m
|
||||||
|
) => Conduit File m SubmissionContent
|
||||||
|
extractRatingsMsg = do
|
||||||
|
ignored' <- filterSubmission `fuseUpstream` Rating.extractRatings
|
||||||
|
let ignored :: Set (Either CryptoFileNameSubmission FilePath)
|
||||||
|
ignored = Right `Set.map` ignored'
|
||||||
|
mr <- (toHtml . ) <$> getMessageRender
|
||||||
|
addMessage "warning" =<< withUrlRenderer ($(ihamletFile "templates/messages/submissionFilesIgnored.hamlet") mr)
|
||||||
|
|
||||||
sinkSubmission :: UserId
|
sinkSubmission :: UserId
|
||||||
-> Either SheetId SubmissionId
|
-> Either SheetId SubmissionId
|
||||||
-> Bool -- ^ Is this a correction
|
-> Bool -- ^ Is this a correction
|
||||||
@ -228,7 +275,7 @@ sinkSubmission userId mExists isUpdate = do
|
|||||||
sinkSubmission' submissionId isUpdate = lift . finalize <=< execStateLC mempty . Conduit.mapM_ $ \case
|
sinkSubmission' submissionId isUpdate = lift . finalize <=< execStateLC mempty . Conduit.mapM_ $ \case
|
||||||
Left file@(File{..}) -> do
|
Left file@(File{..}) -> do
|
||||||
$logDebugS "sinkSubmission" . tshow $ (submissionId, fileTitle)
|
$logDebugS "sinkSubmission" . tshow $ (submissionId, fileTitle)
|
||||||
|
|
||||||
alreadySeen <- gets (Set.member fileTitle . sinkFilenames)
|
alreadySeen <- gets (Set.member fileTitle . sinkFilenames)
|
||||||
when alreadySeen . throwM $ DuplicateFileTitle fileTitle
|
when alreadySeen . throwM $ DuplicateFileTitle fileTitle
|
||||||
tell $ mempty{ sinkFilenames = Set.singleton fileTitle }
|
tell $ mempty{ sinkFilenames = Set.singleton fileTitle }
|
||||||
@ -277,7 +324,9 @@ sinkSubmission userId mExists isUpdate = do
|
|||||||
Right (submissionId', Rating'{..}) -> do
|
Right (submissionId', Rating'{..}) -> do
|
||||||
$logDebugS "sinkSubmission" $ tshow submissionId'
|
$logDebugS "sinkSubmission" $ tshow submissionId'
|
||||||
|
|
||||||
unless (submissionId' == submissionId) $ throwM ForeignRating
|
unless (submissionId' == submissionId) $ do
|
||||||
|
cID <- encrypt submissionId'
|
||||||
|
throwM $ ForeignRating cID
|
||||||
|
|
||||||
alreadySeen <- gets $ getAny . sinkSeenRating
|
alreadySeen <- gets $ getAny . sinkSeenRating
|
||||||
when alreadySeen $ throwM DuplicateRating
|
when alreadySeen $ throwM DuplicateRating
|
||||||
@ -373,6 +422,16 @@ sinkSubmission userId mExists isUpdate = do
|
|||||||
, SubmissionRatingComment =. Nothing
|
, SubmissionRatingComment =. Nothing
|
||||||
]
|
]
|
||||||
|
|
||||||
|
data SubmissionMultiSinkException
|
||||||
|
= SubmissionSinkException
|
||||||
|
{ submissionSinkId :: CryptoFileNameSubmission
|
||||||
|
, submissionSinkFedFile :: Maybe FilePath
|
||||||
|
, submissionSinkException :: SubmissionSinkException
|
||||||
|
}
|
||||||
|
deriving (Typeable, Show)
|
||||||
|
|
||||||
|
instance Exception SubmissionMultiSinkException
|
||||||
|
|
||||||
sinkMultiSubmission :: UserId
|
sinkMultiSubmission :: UserId
|
||||||
-> Bool {-^ Are these corrections -}
|
-> Bool {-^ Are these corrections -}
|
||||||
-> Sink SubmissionContent (YesodDB UniWorX) (Set SubmissionId)
|
-> Sink SubmissionContent (YesodDB UniWorX) (Set SubmissionId)
|
||||||
@ -386,7 +445,9 @@ sinkMultiSubmission userId isUpdate = do
|
|||||||
let
|
let
|
||||||
feed :: SubmissionId
|
feed :: SubmissionId
|
||||||
-> SubmissionContent
|
-> SubmissionContent
|
||||||
-> StateT
|
-> RWST
|
||||||
|
()
|
||||||
|
_
|
||||||
(Map SubmissionId (ResumableSink SubmissionContent (YesodDB UniWorX) SubmissionId))
|
(Map SubmissionId (ResumableSink SubmissionContent (YesodDB UniWorX) SubmissionId))
|
||||||
(YesodDB UniWorX)
|
(YesodDB UniWorX)
|
||||||
()
|
()
|
||||||
@ -396,8 +457,9 @@ sinkMultiSubmission userId isUpdate = do
|
|||||||
Just sink -> return sink
|
Just sink -> return sink
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
lift $ do
|
lift $ do
|
||||||
Submission{..} <- get404 sId
|
|
||||||
cID <- encrypt sId
|
cID <- encrypt sId
|
||||||
|
$(logDebugS) "sinkMultiSubmission" $ "Doing auth checks for " <> toPathPiece cID
|
||||||
|
Submission{..} <- get404 sId
|
||||||
Sheet{..} <- get404 submissionSheet
|
Sheet{..} <- get404 submissionSheet
|
||||||
Course{..} <- get404 sheetCourse
|
Course{..} <- get404 sheetCourse
|
||||||
authRes <- evalAccessDB (CSubmissionR courseTerm courseShorthand sheetName cID CorrectionR) True
|
authRes <- evalAccessDB (CSubmissionR courseTerm courseShorthand sheetName cID CorrectionR) True
|
||||||
@ -410,20 +472,45 @@ sinkMultiSubmission userId isUpdate = do
|
|||||||
case sink' of
|
case sink' of
|
||||||
Left _ -> error "sinkSubmission returned prematurely"
|
Left _ -> error "sinkSubmission returned prematurely"
|
||||||
Right nSink -> modify $ Map.insert sId nSink
|
Right nSink -> modify $ Map.insert sId nSink
|
||||||
sinks <- execStateLC Map.empty . awaitForever $ \case
|
(sinks, ignored) <- execRWSLC () Map.empty . awaitForever $ \case
|
||||||
v@(Right (sId, _)) -> lift $ feed sId v
|
v@(Right (sId, _)) -> do
|
||||||
|
cID <- encrypt sId
|
||||||
|
$logDebugS "sinkMultiSubmission" $ "Feeding rating for " <> toPathPiece cID
|
||||||
|
lift (feed sId v) `catches` [ E.Handler (throwM . SubmissionSinkException cID Nothing), E.Handler (void . handleHCError (Left cID)) ]
|
||||||
(Left f@File{..}) -> do
|
(Left f@File{..}) -> do
|
||||||
let
|
let
|
||||||
tryDecrypt :: FilePath -> _ (Either SomeException SubmissionId)
|
acc :: (Maybe SubmissionId, [FilePath]) -> FilePath -> _ (Maybe SubmissionId, [FilePath])
|
||||||
tryDecrypt (CI.mk -> ciphertext) = try $ decrypt (CryptoID{..} :: CryptoFileNameSubmission)
|
acc (Just sId, fp) segment = return (Just sId, fp ++ [segment])
|
||||||
acc (Just cID, fp) segment = return (Just cID, fp ++ [segment])
|
|
||||||
acc (Nothing , fp) segment = do
|
acc (Nothing , fp) segment = do
|
||||||
msId <- tryDecrypt segment
|
let
|
||||||
return . either (const id) (set _1 . Just) msId $ (Nothing, fp)
|
tryDecrypt ciphertext = do
|
||||||
|
sId <- decrypt (CryptoID (CI.mk segment) :: CryptoFileNameSubmission)
|
||||||
|
Just sId <$ get404 sId
|
||||||
|
msId <- lift (lift $ tryDecrypt segment) `catches` [ E.Handler handleCryptoID, E.Handler (handleHCError $ Right fileTitle) ]
|
||||||
|
return (msId, fp)
|
||||||
(msId, (joinPath -> fileTitle')) <- foldM acc (Nothing, []) $ splitDirectories fileTitle
|
(msId, (joinPath -> fileTitle')) <- foldM acc (Nothing, []) $ splitDirectories fileTitle
|
||||||
$logDebugS "sinkMultiSubmission" $ tshow (splitDirectories fileTitle, msId, fileTitle')
|
case msId of
|
||||||
lift . maybe (const $ return ()) feed msId $ Left f{ fileTitle = fileTitle' }
|
Nothing -> do
|
||||||
fmap Map.keysSet . lift $ mapM (void . closeResumableSink) sinks
|
$logDebugS "sinkMultiSubmission" $ "Dropping " <> tshow (splitDirectories fileTitle, msId, fileTitle')
|
||||||
|
Just sId -> do
|
||||||
|
$logDebugS "sinkMultiSubmission" $ "Feeding " <> tshow (splitDirectories fileTitle, msId, fileTitle')
|
||||||
|
cID <- encrypt sId
|
||||||
|
handle (throwM . SubmissionSinkException cID (Just fileTitle)) $
|
||||||
|
lift . feed sId $ Left f{ fileTitle = fileTitle' }
|
||||||
|
when (not $ null ignored) $ do
|
||||||
|
mr <- (toHtml .) <$> getMessageRender
|
||||||
|
addMessage "warning" =<< withUrlRenderer ($(ihamletFile "templates/messages/submissionFilesIgnored.hamlet") mr)
|
||||||
|
fmap Map.keysSet . lift . sequence $ flip Map.mapWithKey sinks $ \sId sink -> do
|
||||||
|
cID <- encrypt sId
|
||||||
|
handle (throwM . SubmissionSinkException cID Nothing) $
|
||||||
|
void $ closeResumableSink sink
|
||||||
|
where
|
||||||
|
handleHCError :: Either CryptoFileNameSubmission FilePath -> HandlerContents -> _ (Maybe a)
|
||||||
|
handleHCError ident (HCError NotFound) = Nothing <$ tell (Set.singleton ident)
|
||||||
|
handleHCError _ e = throwM e
|
||||||
|
handleCryptoID :: CryptoIDError -> _ (Maybe a)
|
||||||
|
handleCryptoID _ = return Nothing
|
||||||
|
|
||||||
|
|
||||||
submissionMatchesSheet :: TermId -> Text -> Text -> CryptoFileNameSubmission -> DB ()
|
submissionMatchesSheet :: TermId -> Text -> Text -> CryptoFileNameSubmission -> DB ()
|
||||||
submissionMatchesSheet tid csh shn cid = do
|
submissionMatchesSheet tid csh shn cid = do
|
||||||
|
|||||||
39
src/Handler/Utils/Submission/TH.hs
Normal file
39
src/Handler/Utils/Submission/TH.hs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{-# LANGUAGE NoImplicitPrelude
|
||||||
|
, TemplateHaskell
|
||||||
|
, ViewPatterns
|
||||||
|
, OverloadedStrings
|
||||||
|
, StandaloneDeriving
|
||||||
|
, DeriveLift
|
||||||
|
#-}
|
||||||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||||
|
|
||||||
|
module Handler.Utils.Submission.TH
|
||||||
|
( patternFile
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude.Yesod
|
||||||
|
import Language.Haskell.TH
|
||||||
|
import Language.Haskell.TH.Syntax (qAddDependentFile, Lift(..))
|
||||||
|
|
||||||
|
import System.FilePath.Glob
|
||||||
|
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as Text
|
||||||
|
import qualified Data.Text.IO as Text
|
||||||
|
|
||||||
|
deriving instance Lift CompOptions
|
||||||
|
|
||||||
|
-- $(patternFile compDefault file) :: [System.FilePath.Glob.Pattern]
|
||||||
|
patternFile :: CompOptions -> FilePath -> ExpQ
|
||||||
|
patternFile opts file = do
|
||||||
|
qAddDependentFile file
|
||||||
|
patternStrings <- runIO $ filter (not . isComment) . Text.lines <$> Text.readFile file
|
||||||
|
listE $ map (\(Text.unpack -> pat) -> [|compileWith opts pat|]) patternStrings
|
||||||
|
|
||||||
|
isComment :: Text -> Bool
|
||||||
|
isComment line = or
|
||||||
|
[ commentSymbol `Text.isPrefixOf` Text.stripStart line
|
||||||
|
, Text.null $ Text.strip line
|
||||||
|
]
|
||||||
|
where
|
||||||
|
commentSymbol = "$#"
|
||||||
@ -204,7 +204,7 @@ instance Default (PSValidator m x) where
|
|||||||
l <- asks piLimit
|
l <- asks piLimit
|
||||||
case l of
|
case l of
|
||||||
Just l'
|
Just l'
|
||||||
| l' >= 0 -> tell . pure $ SomeMessage MsgPSLimitNonPositive
|
| l' <= 0 -> tell . pure $ SomeMessage MsgPSLimitNonPositive
|
||||||
| otherwise -> modify $ \ps -> ps { psLimit = l' }
|
| otherwise -> modify $ \ps -> ps { psLimit = l' }
|
||||||
Nothing -> return ()
|
Nothing -> return ()
|
||||||
|
|
||||||
@ -242,10 +242,10 @@ class (MonadHandler m, Monoid x, Monoid (DBCell m x)) => IsDBTable (m :: * -> *)
|
|||||||
cellContents :: DBCell m x -> WriterT x m Widget
|
cellContents :: DBCell m x -> WriterT x m Widget
|
||||||
|
|
||||||
cell :: Widget -> DBCell m x
|
cell :: Widget -> DBCell m x
|
||||||
|
|
||||||
|
|
||||||
-- dbWidget :: Proxy m -> Proxy x -> Iso' (DBResult m x) (Widget, DBResult' m x)
|
-- dbWidget :: Proxy m -> Proxy x -> Iso' (DBResult m x) (Widget, DBResult' m x)
|
||||||
dbWidget :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => Proxy m -> Proxy x -> DBResult m x -> m' Widget
|
dbWidget :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => DBTable m x -> DBResult m x -> m' Widget
|
||||||
|
dbHandler :: forall m'. (MonadHandler m' , HandlerSite m' ~ UniWorX) => DBTable m x -> (Widget -> Widget) -> DBResult m x -> m' (DBResult m x)
|
||||||
runDBTable :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => m (x, Widget) -> ReaderT SqlBackend m' (DBResult m x)
|
runDBTable :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => m (x, Widget) -> ReaderT SqlBackend m' (DBResult m x)
|
||||||
|
|
||||||
instance IsDBTable (WidgetT UniWorX IO) () where
|
instance IsDBTable (WidgetT UniWorX IO) () where
|
||||||
@ -262,7 +262,8 @@ instance IsDBTable (WidgetT UniWorX IO) () where
|
|||||||
cell = WidgetCell []
|
cell = WidgetCell []
|
||||||
|
|
||||||
-- dbWidget Proxy Proxy = iso (, ()) $ view _1
|
-- dbWidget Proxy Proxy = iso (, ()) $ view _1
|
||||||
dbWidget Proxy Proxy = return
|
dbWidget _ = return
|
||||||
|
dbHandler _ f x = return $ f x
|
||||||
runDBTable = return . join . fmap (view _2)
|
runDBTable = return . join . fmap (view _2)
|
||||||
|
|
||||||
instance Monoid (DBCell (WidgetT UniWorX IO) ()) where
|
instance Monoid (DBCell (WidgetT UniWorX IO) ()) where
|
||||||
@ -282,7 +283,8 @@ instance IsDBTable (ReaderT SqlBackend (HandlerT UniWorX IO)) () where
|
|||||||
|
|
||||||
cell = DBCell [] . return
|
cell = DBCell [] . return
|
||||||
|
|
||||||
dbWidget Proxy Proxy = return
|
dbWidget _ = return
|
||||||
|
dbHandler _ f x = return $ f x
|
||||||
-- runDBTable :: forall m. (MonadHandler m, HandlerSite m ~ UniWorX) => ReaderT SqlBackend (HandlerT UniWorX IO) ((), Widget) -> m (Widget)
|
-- runDBTable :: forall m. (MonadHandler m, HandlerSite m ~ UniWorX) => ReaderT SqlBackend (HandlerT UniWorX IO) ((), Widget) -> m (Widget)
|
||||||
runDBTable = fmap snd . mapReaderT liftHandlerT
|
runDBTable = fmap snd . mapReaderT liftHandlerT
|
||||||
|
|
||||||
@ -306,7 +308,8 @@ instance Monoid a => IsDBTable (RWST (Maybe (Env, FileEnv), UniWorX, [Lang]) Enc
|
|||||||
|
|
||||||
-- dbWidget Proxy Proxy = iso ((,) <$> view (_1._2) <*> ((,) <$> view (_1._1) <*> view _2))
|
-- dbWidget Proxy Proxy = iso ((,) <$> view (_1._2) <*> ((,) <$> view (_1._1) <*> view _2))
|
||||||
-- ((,) <$> ((,) <$> view (_2._1) <*> view _1) <*> view (_2._2))
|
-- ((,) <$> ((,) <$> view (_2._1) <*> view _1) <*> view (_2._2))
|
||||||
dbWidget Proxy Proxy = liftHandlerT . fmap (view $ _1 . _2) . runFormPost
|
dbWidget DBTable{ dbtIdent = (toPathPiece -> dbtIdent) } = liftHandlerT . fmap (view $ _1 . _2) . runFormPost . identifyForm dbtIdent
|
||||||
|
dbHandler DBTable{ dbtIdent = (toPathPiece -> dbtIdent) } f form = return $ \csrf -> over _2 f <$> identifyForm dbtIdent form csrf
|
||||||
-- runDBTable :: MForm (HandlerT UniWorX IO) (FormResult a, Widget) -> m ((FormResult a, Widget), Enctype)
|
-- runDBTable :: MForm (HandlerT UniWorX IO) (FormResult a, Widget) -> m ((FormResult a, Widget), Enctype)
|
||||||
-- runDBTable form = liftHandlerT . runFormPost $ \html -> over _2 (<> toWidget html) <$> form
|
-- runDBTable form = liftHandlerT . runFormPost $ \html -> over _2 (<> toWidget html) <$> form
|
||||||
-- runDBTable :: MForm (HandlerT UniWorX IO) (FormResult a, Widget) -> m (Html -> MForm (HandleT UniWorX IO) (FormResult a, Widget))
|
-- runDBTable :: MForm (HandlerT UniWorX IO) (FormResult a, Widget) -> m (Html -> MForm (HandleT UniWorX IO) (FormResult a, Widget))
|
||||||
@ -413,16 +416,13 @@ dbTable PSValidator{..} dbtable@(DBTable{ dbtIdent = (toPathPiece -> dbtIdent),
|
|||||||
pageNumbers = [0..pred pageCount]
|
pageNumbers = [0..pred pageCount]
|
||||||
|
|
||||||
return $(widgetFile "table/layout")
|
return $(widgetFile "table/layout")
|
||||||
|
|
||||||
dbWidget' :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => DBResult m x -> m' Widget
|
|
||||||
dbWidget' = dbWidget (Proxy :: Proxy m) (Proxy :: Proxy x)
|
|
||||||
|
|
||||||
bool return (sendResponse <=< tblLayout <=< dbWidget') psShortcircuit <=< runDBTable . fmap swap $ runWriterT table'
|
bool (dbHandler dbtable $ \table -> $(widgetFile "table/layout-wrapper")) (sendResponse <=< tblLayout <=< dbWidget dbtable) psShortcircuit <=< runDBTable . fmap swap $ runWriterT table'
|
||||||
where
|
where
|
||||||
tblLayout :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => Widget -> m' Html
|
tblLayout :: forall m'. (MonadHandler m', HandlerSite m' ~ UniWorX) => Widget -> m' Html
|
||||||
tblLayout tbl' = do
|
tblLayout tbl' = do
|
||||||
tbl <- liftHandlerT $ widgetToPageContent tbl'
|
tbl <- liftHandlerT $ widgetToPageContent tbl'
|
||||||
withUrlRenderer $(hamletFile "templates/table/layout-wrapper.hamlet")
|
withUrlRenderer $(hamletFile "templates/table/layout-standalone.hamlet")
|
||||||
|
|
||||||
setParam :: Text -> Maybe Text -> QueryText -> QueryText
|
setParam :: Text -> Maybe Text -> QueryText -> QueryText
|
||||||
setParam key v qt = (key, v) : [ i | i@(key', _) <- qt, key' /= key ]
|
setParam key v qt = (key, v) : [ i | i@(key', _) <- qt, key' /= key ]
|
||||||
|
|||||||
@ -171,6 +171,8 @@ h4 {
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: padding-left .2s ease-out;
|
transition: padding-left .2s ease-out;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
> .container {
|
> .container {
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
@ -185,6 +187,13 @@ h4 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logged-in {
|
||||||
|
.main__content {
|
||||||
|
margin: 0;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.logged-in {
|
.logged-in {
|
||||||
.main__content {
|
.main__content {
|
||||||
@ -210,7 +219,6 @@ h4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1200px) {
|
@media (min-width: 1200px) {
|
||||||
|
|
||||||
.logged-in {
|
.logged-in {
|
||||||
.main__content {
|
.main__content {
|
||||||
padding-left: 320px;
|
padding-left: 320px;
|
||||||
|
|||||||
9
templates/messages/submissionFilesIgnored.hamlet
Normal file
9
templates/messages/submissionFilesIgnored.hamlet
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
_{MsgSubmissionFilesIgnored}
|
||||||
|
<ul>
|
||||||
|
$forall ident <- ignored
|
||||||
|
$case ident
|
||||||
|
$of Right fileTitle
|
||||||
|
<li style="font-family: monospace">#{fileTitle}
|
||||||
|
$of Left cID
|
||||||
|
<li>Bewertungsdatei für <span style="font-family: monospace">#{toPathPiece cID}</span>
|
||||||
|
|
||||||
1
templates/table/layout-standalone.hamlet
Normal file
1
templates/table/layout-standalone.hamlet
Normal file
@ -0,0 +1 @@
|
|||||||
|
^{pageBody tbl}
|
||||||
@ -1 +1,3 @@
|
|||||||
^{pageBody tbl}
|
$newline never
|
||||||
|
<div ##{wIdent "table-wrapper"}>
|
||||||
|
^{table}
|
||||||
|
|||||||
@ -2,12 +2,11 @@ $newline never
|
|||||||
$if null wRows && (dbsEmptyStyle == DBESNoHeading)
|
$if null wRows && (dbsEmptyStyle == DBESNoHeading)
|
||||||
_{dbsEmptyMessage}
|
_{dbsEmptyMessage}
|
||||||
$else
|
$else
|
||||||
<div ##{wIdent "table-wrapper"}>
|
<div .scrolltable>
|
||||||
<div .scrolltable>
|
^{table}
|
||||||
^{table}
|
$if pageCount > 1
|
||||||
$if pageCount > 1
|
<ul ##{wIdent "pagination"} .pagination>
|
||||||
<ul ##{wIdent "pagination"} .pagination>
|
$forall p <- pageNumbers
|
||||||
$forall p <- pageNumbers
|
<li .pagination-link :p == psPage:.current>
|
||||||
<li .pagination-link :p == psPage:.current>
|
<a href=#{tblLink $ setParam (wIdent "page") (Just $ tshow p)}>
|
||||||
<a href=#{tblLink $ setParam (wIdent "page") (Just $ tshow p)}>
|
_{MsgPage (succ p)}
|
||||||
_{MsgPage (succ p)}
|
|
||||||
|
|||||||
@ -8,9 +8,11 @@
|
|||||||
Vorabversion!
|
Vorabversion!
|
||||||
Die Implementierung von
|
Die Implementierung von
|
||||||
Uni2work ist noch nicht abgeschlossen.
|
Uni2work ist noch nicht abgeschlossen.
|
||||||
^{features}
|
|
||||||
|
|
||||||
<p>
|
<section>
|
||||||
|
^{features}
|
||||||
|
|
||||||
|
<section>
|
||||||
<h2>
|
<h2>
|
||||||
Bekannte Bugs
|
Bekannte Bugs
|
||||||
<ul>
|
<ul>
|
||||||
@ -21,13 +23,13 @@
|
|||||||
<li>
|
<li>
|
||||||
Tabellen über mehrere Seiten müssen vor Seitenwechsel manuell sortiert werden
|
Tabellen über mehrere Seiten müssen vor Seitenwechsel manuell sortiert werden
|
||||||
|
|
||||||
<p>
|
<section>
|
||||||
<h2>
|
<h2>
|
||||||
Versionsgeschichte
|
Versionsgeschichte
|
||||||
<pre #changelog>
|
<p #changelog>
|
||||||
#{changeLog}
|
#{changeLog}
|
||||||
|
|
||||||
<p>
|
<section>
|
||||||
<h2>
|
<h2>
|
||||||
Impressum
|
Impressum
|
||||||
|
|
||||||
@ -46,3 +48,7 @@
|
|||||||
Ludwig-Maximilians-Universität München
|
Ludwig-Maximilians-Universität München
|
||||||
<li>
|
<li>
|
||||||
Oettingenstr. 67, 80538 München
|
Oettingenstr. 67, 80538 München
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<p #gitrev>
|
||||||
|
#{gitInfo}
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
#changelog {
|
#changelog {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
white-space: pre-line;
|
white-space: pre-wrap;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gitrev {
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user