Merge remote-tracking branch 'origin/master' into feat/jobs
This commit is contained in:
commit
0ca12cecdb
@ -59,7 +59,7 @@ colDescription = sortable Nothing (i18nCell MsgCourseDescription)
|
||||
$ \DBRow{ dbrOutput=(Entity cid Course{..}, _, _, _) } ->
|
||||
case courseDescription of
|
||||
Nothing -> mempty
|
||||
(Just descr) -> cell $ modalStatic descr
|
||||
(Just descr) -> cell $ modal "Beschreibung" (Right $ toWidget descr)
|
||||
|
||||
colCShort :: IsDBTable m a => Colonnade _ CourseTableData (DBCell m a)
|
||||
colCShort = sortable (Just "cshort") (i18nCell MsgCourseShort)
|
||||
@ -73,7 +73,11 @@ colCShortDescr = sortable (Just "cshort") (i18nCell MsgCourseShort)
|
||||
( case courseDescription of
|
||||
Nothing -> mempty
|
||||
(Just descr) -> cell
|
||||
[whamlet|<span style="float:right"> ^{modalStatic descr} |]
|
||||
[whamlet|
|
||||
$newline never
|
||||
<span style="float:right">
|
||||
^{modal "Beschreibung" (Right $ toWidget descr)}
|
||||
|]
|
||||
)
|
||||
|
||||
colTerm :: IsDBTable m a => Colonnade _ CourseTableData (DBCell m a)
|
||||
@ -612,5 +616,3 @@ getCUserR tid ssh csh uuid = do
|
||||
|
||||
getCHiWisR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||
getCHiWisR tid ssh csh = undefined -- TODO
|
||||
|
||||
|
||||
|
||||
@ -176,14 +176,15 @@ getProfileDataR = do
|
||||
-- TODO: move this into a Message and/or Widget-File
|
||||
let delWdgt = [whamlet|
|
||||
<form .form-inline method=post action=@{ProfileDataR} enctype=#{btnEnctype}>
|
||||
<div>Sind Sie sich absolut sicher, alle gespeicherten Daten zu löschen?
|
||||
<h2>Sind Sie sich absolut sicher, alle gespeicherten Daten zu löschen?
|
||||
<div .container>
|
||||
Abgegebene Hausaufgaben werden dadurch rückwirkend gelöscht,
|
||||
wodurch eventuell ein Klausurbonus nicht mehr anerkannt wird.
|
||||
<div>
|
||||
<div .container>
|
||||
<em>Gilt nicht in der Testphase von Uni2work:
|
||||
Klausurnoten können Sie hiermit nicht löschen.
|
||||
Da diese 5 Jahre bis nach Ihrer Exmatrikulation aufbewahrt werden müssen.
|
||||
<div>^{btnWdgt}
|
||||
<div .container>^{btnWdgt}
|
||||
|]
|
||||
defaultLayout $ do
|
||||
$(widgetFile "profileData")
|
||||
|
||||
@ -31,7 +31,7 @@ import Handler.Utils.Table.Cells
|
||||
-- import Colonnade hiding (fromMaybe, singleton, bool)
|
||||
import qualified Yesod.Colonnade as Yesod
|
||||
import Text.Blaze (text)
|
||||
--
|
||||
--
|
||||
-- import qualified Data.UUID.Cryptographic as UUID
|
||||
import qualified Data.Conduit.List as C
|
||||
|
||||
@ -451,7 +451,7 @@ handleSheetEdit tid ssh csh msId template dbAction = do
|
||||
whenIsJust sfSolutionF $ insertSheetFile' sid SheetSolution
|
||||
whenIsJust sfMarkingF $ insertSheetFile' sid SheetMarking
|
||||
insert_ $ SheetEdit aid actTime sid
|
||||
addMessageI Info $ MsgSheetEditOk tid ssh csh sfName
|
||||
addMessageI Success $ MsgSheetEditOk tid ssh csh sfName
|
||||
-- Sanity checks generating warnings only, but not errors!
|
||||
warnTermDays tid [sfVisibleFrom, Just sfActiveFrom, Just sfActiveTo, sfHintFrom, sfSolutionFrom]
|
||||
return True
|
||||
@ -522,7 +522,7 @@ insertSheetFile' sid ftype fs = do
|
||||
fid <- insert file
|
||||
void . insert $ SheetFile sid fid ftype -- cannot fail due to uniqueness, since we generated a fresh FileId in the previous step
|
||||
|
||||
|
||||
|
||||
data CorrectorForm = CorrectorForm
|
||||
{ cfUserId :: UserId
|
||||
, cfUserName :: Text
|
||||
@ -547,9 +547,9 @@ defaultLoads shid = do
|
||||
return . E.min_ $ sheetEdit E.^. SheetEditTime
|
||||
|
||||
E.where_ $ sheet E.^. SheetCourse E.==. E.val cId
|
||||
|
||||
|
||||
E.orderBy [E.desc creationTime]
|
||||
|
||||
|
||||
return (sheetCorrector E.^. SheetCorrectorUser, sheetCorrector E.^. SheetCorrectorLoad, sheetCorrector E.^. SheetCorrectorState)
|
||||
where
|
||||
toMap :: [(E.Value UserId, E.Value Load, E.Value CorrectorState)] -> Loads
|
||||
@ -616,11 +616,11 @@ correctorForm shid = do
|
||||
_ -> return loads''
|
||||
|
||||
let deletions' = deletions `Set.difference` Map.keysSet loads
|
||||
|
||||
|
||||
names <- fmap (Map.fromList . map (\(E.Value a, E.Value b) -> (a, b))) . lift . runDB . E.select . E.from $ \user -> do
|
||||
E.where_ $ user E.^. UserId `E.in_` E.valList (Map.keys loads)
|
||||
return $ (user E.^. UserId, user E.^. UserDisplayName)
|
||||
|
||||
|
||||
let
|
||||
constructFields :: (UserId, Text, (CorrectorState, Load)) -> MForm Handler CorrectorForm
|
||||
constructFields (uid, uname, (state, Load{..})) = do
|
||||
@ -691,7 +691,7 @@ correctorForm shid = do
|
||||
|]
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
-- Eingabebox für Korrektor hinzufügen
|
||||
-- Eingabe für Korrekt ausgefüllt: FormMissing zurückschicken um dann Feld hinzuzufügen
|
||||
|
||||
|
||||
@ -76,7 +76,11 @@ courseCell (Course {..}) = anchorCell link name `mappend` desc
|
||||
name = citext2widget courseName
|
||||
desc = case courseDescription of
|
||||
Nothing -> mempty
|
||||
(Just descr) -> cell [whamlet| <span style="float:right"> ^{modalStatic descr} |]
|
||||
(Just descr) -> cell [whamlet|
|
||||
$newline never
|
||||
<span style="float:right">
|
||||
^{modal "Beschreibung" (Right $ toWidget descr)}
|
||||
|]
|
||||
|
||||
sheetCell :: IsDBTable m a => CourseLink -> SheetName -> DBCell m a
|
||||
sheetCell crse shn =
|
||||
|
||||
@ -2,39 +2,19 @@
|
||||
|
||||
module Handler.Utils.Templates where
|
||||
|
||||
import Data.Either (isLeft)
|
||||
|
||||
import Import.NoFoundation
|
||||
|
||||
lipsum :: WidgetT site IO ()
|
||||
lipsum = $(widgetFile "widgets/lipsum")
|
||||
|
||||
modalStatic :: Html -> WidgetT site IO ()
|
||||
modalStatic modalContent = do
|
||||
uniqueId <- newIdent
|
||||
let modalTrigger = cons '#' uniqueId -- SJ: I am confused why this is needed here?
|
||||
modalId :: Int32
|
||||
modalId = 13
|
||||
$(widgetFile "widgets/modalStatic")
|
||||
[whamlet|<div .tooltip__handle ##{uniqueId}>?|] -- SJ: confused why ## is needed here either?
|
||||
|
||||
modalWidget :: Html -> WidgetT site IO () -> WidgetT site IO ()
|
||||
modalWidget modalTrigger modalContent = do
|
||||
uniqueId <- newIdent
|
||||
let modalTriggerId = cons '#' uniqueId -- SJ: I am confused why this is needed here?
|
||||
modalId :: Int32
|
||||
modalId = 13
|
||||
$(widgetFile "widgets/modalWidget")
|
||||
[whamlet|<div .btn ##{uniqueId}>#{modalTrigger}|] -- SJ: confused why ## is needed here either?
|
||||
|
||||
modal :: Text -> Maybe [Char] -> WidgetT site IO ()
|
||||
modal modalTrigger (Just modalContent) = do -- WARNING: ModalContent should not have length 11. SJ: This is possibly bad. See Template!
|
||||
let
|
||||
modalId :: Int32
|
||||
modalId = 13
|
||||
$(widgetFile "widgets/modal")
|
||||
modal modalTrigger Nothing = do
|
||||
let
|
||||
modalId :: Int32
|
||||
modalId = 13
|
||||
modalContent :: [Char]
|
||||
modalContent = "placeholder"
|
||||
modal :: WidgetT site IO () -> Either (Route site) (WidgetT site IO ()) -> WidgetT site IO ()
|
||||
modal modalTrigger modalContent = do
|
||||
let modalDynamic = isLeft modalContent
|
||||
modalId <- newIdent
|
||||
triggerId <- newIdent
|
||||
$(widgetFile "widgets/modal")
|
||||
case modalContent of
|
||||
Left route -> [whamlet|<a .btn ##{triggerId} href=@{route}>^{modalTrigger}|]
|
||||
Right content -> [whamlet|<div .btn ##{triggerId}>^{modalTrigger}|]
|
||||
|
||||
@ -22,7 +22,6 @@
|
||||
<li .list-group-item>
|
||||
<a href=@{CourseNewR}>Kurse anlegen
|
||||
|
||||
<hr>
|
||||
<div .container>
|
||||
<h2>Funktionen zum Testen
|
||||
|
||||
@ -33,12 +32,8 @@
|
||||
^{btnWdgt}
|
||||
<li><br>
|
||||
Modals:
|
||||
^{modal ".toggler1" Nothing}
|
||||
<a href=@{UsersR} .btn.toggler1>Klick mich für Ajax-Test
|
||||
<noscript>(Für Modals bitte JS aktivieren)</noscript>
|
||||
^{modal ".toggler2" (Just "Test Inhalt für Modal")}
|
||||
<div .btn.toggler2>Klick mich für Content-Test
|
||||
<noscript>(Für Modals bitte JS aktivieren)</noscript>
|
||||
^{modal "Klick mich für Ajax-Test" (Left UsersR)}
|
||||
^{modal "Klick mich für Content-Test" (Right "Test Inhalt für Modal")}
|
||||
<li>
|
||||
<form method=post action=@{AdminTestR} enctype=#{emailEnctype}>
|
||||
^{emailWidget}
|
||||
|
||||
@ -7,38 +7,11 @@ $newline never
|
||||
<html class="no-js" lang="en"> <!--<![endif]-->
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<title>#{pageTitle pc}
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
|
||||
^{pageHead pc}
|
||||
|
||||
\<!--[if lt IE 9]>
|
||||
\<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
||||
\<![endif]-->
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/js-cookie/2.0.3/js.cookie.min.js">
|
||||
<script>
|
||||
/* The `defaultCsrfMiddleware` Middleware added in Foundation.hs adds a CSRF token to the request cookies. */
|
||||
/* AJAX requests should add that token to a header to be validated by the server. */
|
||||
/* See the CSRF documentation in the Yesod.Core.Handler module of the yesod-core package for details. */
|
||||
var csrfHeaderName = "#{TE.decodeUtf8 $ CI.foldedCase defaultCsrfHeaderName}";
|
||||
|
||||
var csrfCookieName = "#{TE.decodeUtf8 defaultCsrfCookieName}";
|
||||
var csrfToken = Cookies.get(csrfCookieName);
|
||||
|
||||
|
||||
if (csrfToken) {
|
||||
\ $.ajaxPrefilter(function( options, originalOptions, jqXHR ) {
|
||||
\ if (!options.crossDomain) {
|
||||
\ jqXHR.setRequestHeader(csrfHeaderName, csrfToken);
|
||||
\ }
|
||||
\ });
|
||||
}
|
||||
|
||||
|
||||
<body .no-js .theme--#{toPathPiece currentTheme} :isAuth:.logged-in>
|
||||
<!-- removes no-js class from body if client supports javascript -->
|
||||
<script>
|
||||
|
||||
@ -113,7 +113,7 @@ a:hover {
|
||||
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
@ -424,8 +424,17 @@ input[type="button"].btn-info:hover,
|
||||
}
|
||||
|
||||
/* LIST MODIFIERS */
|
||||
.list--inline li {
|
||||
display: inline-block;
|
||||
.list--iconless {
|
||||
list-style-type: none;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.list--inline {
|
||||
margin-left: 0;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.list--comma-separated li {
|
||||
|
||||
@ -16,5 +16,5 @@ $# new files
|
||||
<label for=#{fieldId}_zip>ZIPs automatisch entpacken
|
||||
<input type=checkbox id=#{fieldId}_zip name=#{fieldName} value=#{unpackZips}>
|
||||
<div class="js-tooltip">
|
||||
<div class="tooltip__handle">?
|
||||
<div class="tooltip__handle">
|
||||
<div class="tooltip__content">Entpackt hochgeladene Zip-Dateien (*.zip) automatisch und fügt den Inhalt dem Stamm-Verzeichnis der Abgabe hinzu.
|
||||
|
||||
@ -51,7 +51,7 @@
|
||||
.
|
||||
|
||||
<h2>
|
||||
^{modalWidget "Alle Benutzerbezogenen Daten löschen" delWdgt}
|
||||
^{modal "Alle Benutzerbezogenen Daten löschen" (Right delWdgt)}
|
||||
<p>
|
||||
<h4>Hinweise:
|
||||
<ul>
|
||||
|
||||
@ -3,25 +3,75 @@
|
||||
|
||||
window.utils = window.utils || {};
|
||||
|
||||
window.utils.alert = function(alertEl) {
|
||||
var closeEl = document.createElement('DIV');
|
||||
var dataDecay = alertEl.dataset.decay;
|
||||
var autoDecay = 30;
|
||||
if (dataDecay) {
|
||||
autoDecay = parseInt(dataDecay, 10);
|
||||
}
|
||||
closeEl.classList.add('alert__close');
|
||||
closeEl.addEventListener('click', function(event) {
|
||||
alertEl.classList.add('alert--invisible');
|
||||
});
|
||||
alertEl.insertBefore(closeEl, alertEl.children[0]);
|
||||
var ALERT_INVISIBLE_CLASS = 'alert--invisible';
|
||||
var TOGGLER_INVISIBLE_CLASS = 'alerts__toggler--visible';
|
||||
|
||||
// auto-hide info and success-alerts after 3 seconds
|
||||
if (autoDecay > 0 && !alertEl.matches('.alert-danger, .alert-warning')) {
|
||||
window.setTimeout(function() {
|
||||
alertEl.classList.add('alert--invisible');
|
||||
}, autoDecay * 1000);
|
||||
window.utils.alerts = function(alertsEl) {
|
||||
|
||||
var alerts = Array.from(alertsEl.querySelectorAll('.alert'));
|
||||
var toggler;
|
||||
var showingToggler = false;
|
||||
|
||||
function makeToggler() {
|
||||
toggler = document.createElement('DIV');
|
||||
toggler.classList.add('alerts__toggler');
|
||||
toggler.addEventListener('click', function() {
|
||||
alerts.forEach(function(alert) {
|
||||
alert.classList.remove(ALERT_INVISIBLE_CLASS);
|
||||
toggler.classList.remove(TOGGLER_INVISIBLE_CLASS);
|
||||
});
|
||||
checkToggler();
|
||||
});
|
||||
alertsEl.appendChild(toggler);
|
||||
}
|
||||
|
||||
function makeAlert(alertEl) {
|
||||
var iconEl = document.createElement('DIV');
|
||||
var closeEl = document.createElement('DIV');
|
||||
var dataDecay = alertEl.dataset.decay;
|
||||
var autoDecay = 30;
|
||||
if (dataDecay) {
|
||||
autoDecay = parseInt(dataDecay, 10);
|
||||
}
|
||||
iconEl.classList.add('alert__icon');
|
||||
closeEl.classList.add('alert__close');
|
||||
closeEl.addEventListener('click', function(event) {
|
||||
closeAlert(alertEl);
|
||||
});
|
||||
alertEl.insertBefore(iconEl, alertEl.children[0]);
|
||||
alertEl.insertBefore(closeEl, alertEl.children[0]);
|
||||
|
||||
// auto-hide info and success-alerts after 3 seconds
|
||||
if (autoDecay > 0 && !alertEl.matches('.alert-warning, .alert-error')) {
|
||||
window.setTimeout(function() {
|
||||
closeAlert(alertEl);
|
||||
}, autoDecay * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function closeAlert(alertEl) {
|
||||
alertEl.classList.add(ALERT_INVISIBLE_CLASS);
|
||||
checkToggler();
|
||||
}
|
||||
|
||||
function checkToggler() {
|
||||
var hidden = true;
|
||||
alerts.forEach(function(alert) {
|
||||
if (hidden && !alert.classList.contains(ALERT_INVISIBLE_CLASS)) {
|
||||
hidden = false;
|
||||
}
|
||||
});
|
||||
if (!showingToggler) {
|
||||
showingToggler = true;
|
||||
window.setTimeout(function() {
|
||||
toggler.classList.toggle(TOGGLER_INVISIBLE_CLASS, hidden);
|
||||
showingToggler = false;
|
||||
}, 120);
|
||||
}
|
||||
}
|
||||
|
||||
makeToggler();
|
||||
alerts.map(makeAlert);
|
||||
}
|
||||
|
||||
})();
|
||||
@ -29,7 +79,5 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// setup alerts
|
||||
Array.from(document.querySelectorAll('.alert')).forEach(function(alertEl) {
|
||||
window.utils.alert(alertEl);
|
||||
});
|
||||
window.utils.alerts(document.querySelector('.alerts'));
|
||||
});
|
||||
|
||||
@ -3,100 +3,152 @@
|
||||
.alert
|
||||
Regular Info Alert
|
||||
Disappears automatically after 30 seconds
|
||||
Disappears after x seconds if explicitly specified via data-decay='x' on html element
|
||||
Disappears after x seconds if explicitly specified via data-decay='x'
|
||||
Can be told not to disappear with data-decay='0'
|
||||
|
||||
.alert-warning, .alert-error
|
||||
Warning or Error alert
|
||||
These don't disappear, only difference is color
|
||||
.alert-warning is orange regardless of user's selected theme
|
||||
.alert-error is red regardless of user's selected theme
|
||||
.alert-success
|
||||
Disappears automatically after 30 seconds
|
||||
|
||||
.alert-warning
|
||||
Does not disappear
|
||||
Orange regardless of user's selected theme
|
||||
|
||||
.alert-error
|
||||
Does not disappear
|
||||
Red regardless of user's selected theme
|
||||
|
||||
*/
|
||||
.alerts {
|
||||
position: fixed;
|
||||
bottom: 5%;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
right: 5%;
|
||||
z-index: 20;
|
||||
text-align: right;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.alerts__toggler {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
top: 400px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
cursor: pointer;
|
||||
|
||||
&::before {
|
||||
content: '\f077';
|
||||
position: absolute;
|
||||
font-family: "Font Awesome 5 Free";
|
||||
left: 50%;
|
||||
top: 0;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 30px;
|
||||
color: var(--color-lightblack);
|
||||
font-size: 30px;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.alerts__toggler--visible {
|
||||
top: -40px;
|
||||
opacity: 1;
|
||||
transition: top .5s cubic-bezier(0.73, 1.25, 0.61, 1),
|
||||
opacity .5s cubic-bezier(0.73, 1.25, 0.61, 1);
|
||||
}
|
||||
|
||||
@media (max-width: 425px) {
|
||||
|
||||
.alerts {
|
||||
left: 5%;
|
||||
}
|
||||
}
|
||||
|
||||
.alert {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
background-color: var(--color-dark);
|
||||
display: block;
|
||||
background-color: var(--color-lightblack);
|
||||
font-size: 1rem;
|
||||
color: var(--color-lightwhite);
|
||||
z-index: 0;
|
||||
max-height: 200px;
|
||||
transition: all .3s ease-in-out;
|
||||
padding-left: 20px;
|
||||
margin-left: 20px;
|
||||
padding: 0 50px;
|
||||
padding-right: 60px;
|
||||
animation: slide-in-alert .2s ease-out forwards;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:hover {
|
||||
|
||||
.alert__content {
|
||||
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
transition: margin-bottom .2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slide-in-alert {
|
||||
from {
|
||||
left: 120%;
|
||||
transform: translateY(120%);
|
||||
}
|
||||
to {
|
||||
left: 0;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slide-out-alert {
|
||||
from {
|
||||
transform: translateY(0);
|
||||
max-height: 200px;
|
||||
}
|
||||
to {
|
||||
transform: translateY(250%);
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 425px) {
|
||||
|
||||
.alert {
|
||||
margin-left: 80px;
|
||||
max-width: 420px;
|
||||
max-width: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
|
||||
.alert {
|
||||
padding-left: 30px;
|
||||
margin-left: 40px;
|
||||
min-width: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
|
||||
.alert {
|
||||
min-width: 350px;
|
||||
}
|
||||
.alert--invisible {
|
||||
animation: slide-out-alert .2s ease-out forwards;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.alert__content {
|
||||
padding: 8px 1.5em;
|
||||
padding: 8px 0;
|
||||
min-height: 40px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
font-weight: 600;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.alert__icon {
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0;
|
||||
width: 50px;
|
||||
height: 100%;
|
||||
z-index: 40;
|
||||
|
||||
.alert__content {
|
||||
padding: 4px 7px;
|
||||
padding-left: 25px;
|
||||
&::before {
|
||||
content: '\f05a';
|
||||
position: absolute;
|
||||
font-family: "Font Awesome 5 Free";
|
||||
font-size: 24px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,7 +156,7 @@
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
top: 0;
|
||||
width: 60px;
|
||||
height: 100%;
|
||||
@ -145,18 +197,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: var(--color-success);
|
||||
|
||||
.alert__icon::before {
|
||||
content: '\f058';
|
||||
}
|
||||
}
|
||||
|
||||
.alert-warning {
|
||||
background-color: var(--color-warning);
|
||||
|
||||
.alert__icon::before {
|
||||
content: '\f06a';
|
||||
}
|
||||
}
|
||||
|
||||
.alert-danger,
|
||||
.alert-error {
|
||||
background-color: var(--color-error);
|
||||
}
|
||||
|
||||
.alert--invisible {
|
||||
max-height: 0;
|
||||
transform: translateX(120%);
|
||||
margin-bottom: 0;
|
||||
overflow: hidden;
|
||||
.alert__icon::before {
|
||||
content: '\f071';
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +30,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.form-group--submit .form-group__input {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.form-group--submit .form-group__input {
|
||||
grid-column: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.form-group--has-error {
|
||||
background-color: rgba(255, 0, 0, 0.1);
|
||||
|
||||
@ -187,6 +197,7 @@ input[type="checkbox"]:checked::after {
|
||||
.checkbox,
|
||||
.radio {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
|
||||
@ -6,12 +6,11 @@
|
||||
window.utils.modal = function(modal) {
|
||||
var overlay = document.createElement('div');
|
||||
var closer = document.createElement('div');
|
||||
var trigger = document.querySelector(modal.dataset.trigger);
|
||||
var trigger = document.querySelector('#' + modal.dataset.trigger);
|
||||
var origParent = modal.parentNode;
|
||||
|
||||
function open(event) {
|
||||
// disable modals for narrow screens
|
||||
if (window.innerWidth < 768) return true;
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
@ -20,7 +19,6 @@
|
||||
document.body.insertBefore(modal, null);
|
||||
document.body.insertBefore(overlay, modal);
|
||||
overlay.classList.add('modal__overlay--open');
|
||||
toggleScroll(false);
|
||||
|
||||
if (modal.dataset.closeable === 'true') {
|
||||
closer.classList.add('modal__closer');
|
||||
@ -30,8 +28,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
// open this modal with an event:
|
||||
// document.dispatchEvent(new CustomEvent('modal-open', { dateils: {for: 'modal-13'}}))
|
||||
// you can open this modal via event
|
||||
// example: document.dispatchEvent(new CustomEvent('modal-open', { details: { for: 'modal-[id]' }}))
|
||||
function openOnEvent(event) {
|
||||
if (event.detail.for === modal.getAttribute('id')) {
|
||||
open();
|
||||
@ -43,7 +41,6 @@
|
||||
overlay.remove();
|
||||
origParent.insertBefore(modal, null);
|
||||
modal.classList.remove('modal--open');
|
||||
toggleScroll(true);
|
||||
closer.removeEventListener('click', close, false);
|
||||
}
|
||||
};
|
||||
@ -56,27 +53,20 @@
|
||||
trigger.classList.add('modal__trigger');
|
||||
trigger.addEventListener('click', open, false);
|
||||
}
|
||||
// if there is no content specified for the modal we assume that
|
||||
// the content is supposed to be the page the trigger links to.
|
||||
// so we check if the trigger has a href-attribute, fetch that page
|
||||
// and replace the modal content with the response
|
||||
var replaceMe = modal.querySelector('.replace-me');
|
||||
var replaceWith = trigger ? trigger.getAttribute('href') : '';
|
||||
if (replaceMe) {
|
||||
replaceMe.classList.remove('replace-me');
|
||||
replaceMe.innerText = '...loading';
|
||||
if (replaceWith.length > 0) {
|
||||
fetch(replaceWith, {
|
||||
credentials: 'same-origin'
|
||||
|
||||
if (modal.dataset.dynamic === 'True') {
|
||||
var dynamicContentURL = trigger.getAttribute('href');
|
||||
if (dynamicContentURL.length > 0) {
|
||||
fetch(dynamicContentURL, {
|
||||
credentials: 'same-origin',
|
||||
}).then(function(response) {
|
||||
return response.text();
|
||||
}).then(function(body) {
|
||||
var modalContent = document.createElement('div');
|
||||
modalContent.innerHTML = body;
|
||||
var main = modalContent.querySelector('.main__content');
|
||||
var main = modalContent.querySelector('.main__content-body');
|
||||
if (main) {
|
||||
replaceMe.innerText = '';
|
||||
replaceMe.insertBefore(main, null);
|
||||
modal.appendChild(main);
|
||||
} else {
|
||||
replaceMe.innerHTML = body;
|
||||
}
|
||||
@ -88,11 +78,6 @@
|
||||
}
|
||||
setup();
|
||||
};
|
||||
|
||||
// make sure document doesn't scroll when modal is active
|
||||
function toggleScroll(scrollable) {
|
||||
document.body.classList.toggle('no-scroll', !scrollable);
|
||||
}
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
@ -4,14 +4,15 @@
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%) scale(0.8, 0.8);
|
||||
display: block;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
background-color: rgba(255, 255, 255, 0.99);
|
||||
min-width: 60vw;
|
||||
min-height: 100px;
|
||||
max-height: calc(100vh - 30px);
|
||||
border-radius: 7px;
|
||||
border-radius: 2px;
|
||||
z-index: -1;
|
||||
color: var(--color-font);
|
||||
padding: 20px;
|
||||
padding-right: 65px;
|
||||
overflow: auto;
|
||||
opacity: 0;
|
||||
transition: all .15s ease;
|
||||
@ -81,7 +82,3 @@
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.no-scroll {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@ -19,6 +19,23 @@
|
||||
text-align: center;
|
||||
margin: 0 10px;
|
||||
cursor: default;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '\f128';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-family: "Font Awesome 5 Free";
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-light);
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip__content {
|
||||
|
||||
@ -9,13 +9,13 @@ $newline never
|
||||
_{MsgWinterTermShort year}
|
||||
$of Summer
|
||||
_{MsgSummerTermShort year}
|
||||
<ul .asidenav__list.js-show-hide__target>
|
||||
<ul .asidenav__list.js-show-hide__target.list--iconless>
|
||||
$forall (Course{..}, courseRoute, pageActions) <- favouriteTerm tid
|
||||
<li .asidenav__list-item :highlight courseRoute:.asidenav__list-item--active>
|
||||
<a .asidenav__link-wrapper href=@{courseRoute}>
|
||||
<div .asidenav__link-shorthand>#{courseShorthand}
|
||||
<div .asidenav__link-label>#{courseName}
|
||||
<ul .asidenav__nested-list>
|
||||
<ul .asidenav__nested-list.list--iconless>
|
||||
$forall action <- pageActions
|
||||
$case action
|
||||
$of PageActionPrime (MenuItem{..})
|
||||
|
||||
@ -3,6 +3,7 @@ $newline never
|
||||
$case formLayout
|
||||
$of FormStandard
|
||||
$forall view <- views
|
||||
$# TODO: add class 'form-group--submit' if this is the submit-button view
|
||||
<div .form-group :fvRequired view:.form-group--required :not $ fvRequired view:.form-group--optional :isJust $ fvErrors view:.form-group--has-error>
|
||||
$if not (Blaze.null $ fvLabel view)
|
||||
<label .form-group__label for=#{fvId view}>#{fvLabel view}
|
||||
@ -10,5 +11,5 @@ $case formLayout
|
||||
^{fvInput view}
|
||||
$maybe tooltip <- fvTooltip view
|
||||
<div .js-tooltip>
|
||||
<div .tooltip__handle>?
|
||||
<div .tooltip__handle>
|
||||
<div .tooltip__content>^{tooltip}
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
<div .modal.js-modal #modal-#{modalId} data-trigger=#{modalTrigger} data-closeable=true>
|
||||
$# primitive way of checking if this is supposed to be add a placeholder for async data.
|
||||
$# modalContent is 'placeholder' if there should be a placeholder only.
|
||||
$# 'placeholder' has length 11.
|
||||
$if 11 == length modalContent
|
||||
<div .replace-me>
|
||||
$else
|
||||
#{modalContent}
|
||||
<div .modal.js-modal #modal-#{modalId} data-trigger=#{triggerId} data-closeable=true data-dynamic=#{modalDynamic}>
|
||||
$case modalContent
|
||||
$of Right content
|
||||
^{content}
|
||||
$of Left _
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
<div .modal.js-modal #modal-#{modalId} data-trigger=#{modalTrigger} data-closeable=true>
|
||||
#{modalContent}
|
||||
@ -1,2 +0,0 @@
|
||||
<div .modal.js-modal #modal-#{modalId} data-trigger=#{modalTriggerId} data-closeable=true>
|
||||
^{modalContent}
|
||||
@ -41,7 +41,7 @@
|
||||
background: linear-gradient(to top, var(--color-dark) 0%,var(--color-darker) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */
|
||||
color: white;
|
||||
margin-right: 40px;
|
||||
z-index: 10;
|
||||
z-index: 20;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
|
||||
overflow: auto;
|
||||
transition: all .2s cubic-bezier(0.03, 0.43, 0.58, 1);
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
.pagenav__list {
|
||||
display: block;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.pagenav__list-item {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user