Merge remote-tracking branch 'origin/master' into feat/jobs

This commit is contained in:
Gregor Kleen 2018-10-08 09:39:27 +02:00
commit 0ca12cecdb
23 changed files with 287 additions and 210 deletions

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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 =

View File

@ -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}|]

View File

@ -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}

View File

@ -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>

View File

@ -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 {

View File

@ -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.

View File

@ -51,7 +51,7 @@
.
<h2>
^{modalWidget "Alle Benutzerbezogenen Daten löschen" delWdgt}
^{modal "Alle Benutzerbezogenen Daten löschen" (Right delWdgt)}
<p>
<h4>Hinweise:
<ul>

View File

@ -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'));
});

View File

@ -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';
}
}

View File

@ -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"] {

View File

@ -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() {

View File

@ -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;
}

View File

@ -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 {

View File

@ -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{..})

View File

@ -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}

View File

@ -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 _

View File

@ -1,2 +0,0 @@
<div .modal.js-modal #modal-#{modalId} data-trigger=#{modalTrigger} data-closeable=true>
#{modalContent}

View File

@ -1,2 +0,0 @@
<div .modal.js-modal #modal-#{modalId} data-trigger=#{modalTriggerId} data-closeable=true>
^{modalContent}

View File

@ -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);

View File

@ -6,6 +6,7 @@
.pagenav__list {
display: block;
margin-left: 0;
}
.pagenav__list-item {