Merge branch 'master' into 'live'
Master Closes #230 See merge request !105
This commit is contained in:
commit
c466a6fa05
@ -108,6 +108,7 @@ dependencies:
|
||||
- mmorph
|
||||
- clientsession
|
||||
- monad-memo
|
||||
- xss-sanitize
|
||||
|
||||
other-extensions:
|
||||
- GeneralizedNewtypeDeriving
|
||||
|
||||
@ -16,8 +16,6 @@ import Auth.PWHash
|
||||
import Auth.Dummy
|
||||
import Jobs.Types
|
||||
|
||||
import Handler.Utils.Templates (siteModalId, modalParameter)
|
||||
|
||||
import qualified Network.Wai as W (pathInfo)
|
||||
|
||||
import Yesod.Default.Util (addStaticContentExternal)
|
||||
@ -768,7 +766,7 @@ siteLayout headingOverride widget = do
|
||||
master <- getYesod
|
||||
let AppSettings { appUserDefaults = UserDefaultConf{..}, .. } = appSettings master
|
||||
|
||||
isModal <- isJust <$> siteModalId
|
||||
isModal <- hasCustomHeader HeaderIsModal
|
||||
|
||||
mcurrentRoute <- getCurrentRoute
|
||||
|
||||
@ -806,7 +804,7 @@ siteLayout headingOverride widget = do
|
||||
return (c, courseRoute, items')
|
||||
|
||||
mmsgs <- if
|
||||
| isModal -> return []
|
||||
| isModal -> getMessages
|
||||
| otherwise -> do
|
||||
applySystemMessages
|
||||
authTagPivots <- fromMaybe Set.empty <$> getSessionJson SessionInactiveAuthTags
|
||||
@ -987,7 +985,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the
|
||||
{ menuItemType = NavbarRight
|
||||
, menuItemLabel = MsgMenuHelp
|
||||
, menuItemIcon = Just "question"
|
||||
, menuItemRoute = SomeRoute (HelpR, catMaybes [("site", ) . toPathPiece <$> mCurrentRoute])
|
||||
, menuItemRoute = SomeRoute (HelpR, catMaybes [(toPathPiece GetReferer, ) . toPathPiece <$> mCurrentRoute])
|
||||
, menuItemModal = True
|
||||
, menuItemAccessCallback' = return True
|
||||
}
|
||||
|
||||
@ -257,31 +257,24 @@ getHelpR, postHelpR :: Handler Html
|
||||
getHelpR = postHelpR
|
||||
postHelpR = do
|
||||
mUid <- maybeAuthId
|
||||
mReferer <- flip formResultMaybe return <=< runInputGetResult $ iopt routeField "site"
|
||||
mReferer <- flip formResultMaybe return <=< runInputGetResult $ iopt routeField (toPathPiece GetReferer)
|
||||
|
||||
((res,formWidget),formEnctype) <- runFormPost $ renderAForm FormStandard $ helpForm mReferer mUid
|
||||
|
||||
case res of
|
||||
FormSuccess HelpForm{..} -> do
|
||||
now <- liftIO getCurrentTime
|
||||
hfReferer' <- traverse toTextUrl hfReferer
|
||||
queueJob' JobHelpRequest
|
||||
{ jSender = hfUserId
|
||||
, jHelpRequest = hfRequest
|
||||
, jRequestTime = now
|
||||
, jReferer = hfReferer'
|
||||
}
|
||||
-- redirect $ HelpR
|
||||
addMessageI Success MsgHelpSent
|
||||
return ()
|
||||
{-selectRep $ do
|
||||
provideJson ()
|
||||
provideRep (redirect $ HelpR :: Handler Html) -}
|
||||
FormMissing -> return ()
|
||||
FormFailure errs -> mapM_ (addMessage Error . toHtml) errs
|
||||
formResultModal res HelpR $ \HelpForm{..} -> do
|
||||
now <- liftIO getCurrentTime
|
||||
hfReferer' <- traverse toTextUrl hfReferer
|
||||
queueJob' JobHelpRequest
|
||||
{ jSender = hfUserId
|
||||
, jHelpRequest = hfRequest
|
||||
, jRequestTime = now
|
||||
, jReferer = hfReferer'
|
||||
}
|
||||
tell . pure =<< messageI Success MsgHelpSent
|
||||
|
||||
defaultLayout $ do
|
||||
setTitle "Hilfe" -- TODO: International
|
||||
isModal <- hasCustomHeader HeaderIsModal
|
||||
$(widgetFile "help")
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
module Handler.Utils.Form
|
||||
( module Handler.Utils.Form
|
||||
, module Utils.Form
|
||||
, MonadWriter(..)
|
||||
) where
|
||||
|
||||
import Utils.Form
|
||||
@ -35,6 +36,7 @@ import qualified Data.Set as Set
|
||||
import Data.Map (Map, (!))
|
||||
import qualified Data.Map as Map
|
||||
|
||||
import Control.Monad.Trans.Writer (execWriterT, WriterT)
|
||||
import Control.Monad.Writer.Class
|
||||
|
||||
import Data.Scientific (Scientific)
|
||||
@ -587,5 +589,16 @@ multiActionA FieldSettings{..} acts defAction = formToAForm $ do
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
|
||||
formResultModal :: (MonadHandler m, RedirectUrl (HandlerSite m) route) => FormResult a -> route -> (a -> WriterT [Message] m ()) -> m ()
|
||||
formResultModal res finalDest handler = maybeT_ $ do
|
||||
messages <- case res of
|
||||
FormMissing -> mzero
|
||||
FormFailure errs -> mapM_ (addMessage Error . toHtml) errs >> mzero
|
||||
FormSuccess val -> lift . execWriterT $ handler val
|
||||
|
||||
isModal <- hasCustomHeader HeaderIsModal
|
||||
if
|
||||
| isModal -> sendResponse $ toJSON messages
|
||||
| otherwise -> do
|
||||
forM_ messages $ \Message{..} -> addMessage messageClass messageContent
|
||||
redirect finalDest
|
||||
|
||||
@ -7,12 +7,6 @@ import Import.NoFoundation
|
||||
lipsum :: WidgetT site IO ()
|
||||
lipsum = $(widgetFile "widgets/lipsum")
|
||||
|
||||
modalParameter :: Text
|
||||
modalParameter = "_modal"
|
||||
|
||||
siteModalId :: MonadHandler m => m (Maybe Text)
|
||||
siteModalId = lookupGetParam modalParameter
|
||||
|
||||
modal :: WidgetT site IO () -> Either (Route site) (WidgetT site IO ()) -> WidgetT site IO ()
|
||||
modal modalTrigger modalContent = do
|
||||
let modalDynamic = isLeft modalContent
|
||||
|
||||
36
src/Utils.hs
36
src/Utils.hs
@ -52,6 +52,8 @@ import Text.Shakespeare.Text (st)
|
||||
|
||||
import qualified Data.Aeson as Aeson
|
||||
|
||||
import Data.Universe
|
||||
|
||||
|
||||
|
||||
-----------
|
||||
@ -476,3 +478,37 @@ tellSessionJson key val = modifySessionJson key $ Just . (`mappend` val) . fromM
|
||||
getSessionJson :: (PathPiece k, FromJSON v, MonadHandler m) => k -> m (Maybe v)
|
||||
-- ^ `lookupSessionJson` followed by `deleteSession`
|
||||
getSessionJson key = lookupSessionJson key <* deleteSession (toPathPiece key)
|
||||
|
||||
--------------------
|
||||
-- GET Parameters --
|
||||
--------------------
|
||||
|
||||
data GlobalGetParam = GetReferer
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
||||
|
||||
instance Universe GlobalGetParam
|
||||
instance Finite GlobalGetParam
|
||||
nullaryPathPiece ''GlobalGetParam (camelToPathPiece' 1)
|
||||
|
||||
lookupGlobalGetParam :: (MonadHandler m, PathPiece result) => GlobalGetParam -> m (Maybe result)
|
||||
lookupGlobalGetParam ident = (>>= fromPathPiece) <$> lookupGetParam (toPathPiece ident)
|
||||
|
||||
hasGlobalGetParam :: MonadHandler m => GlobalGetParam -> m Bool
|
||||
hasGlobalGetParam ident = isJust <$> lookupGetParam (toPathPiece ident)
|
||||
|
||||
---------------------------------
|
||||
-- Custom HTTP Request-Headers --
|
||||
---------------------------------
|
||||
|
||||
data CustomHeader = HeaderIsModal
|
||||
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
|
||||
|
||||
instance Universe CustomHeader
|
||||
instance Finite CustomHeader
|
||||
nullaryPathPiece ''CustomHeader (intercalate "-" . drop 1 . splitCamel)
|
||||
|
||||
lookupCustomHeader :: (MonadHandler m, PathPiece result) => CustomHeader -> m (Maybe result)
|
||||
lookupCustomHeader ident = (>>= fromPathPiece . decodeUtf8) <$> lookupHeader (CI.mk . encodeUtf8 $ toPathPiece ident)
|
||||
|
||||
hasCustomHeader :: MonadHandler m => CustomHeader -> m Bool
|
||||
hasCustomHeader ident = isJust <$> lookupHeader (CI.mk . encodeUtf8 $ toPathPiece ident)
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
module Utils.Message
|
||||
( MessageClass(..)
|
||||
, UnknownMessageClass(..)
|
||||
, addMessage, addMessageI, addMessageIHamlet, addMessageFile, addMessageWidget
|
||||
, Message(..)
|
||||
, messageI, messageIHamlet, messageFile, messageWidget
|
||||
) where
|
||||
|
||||
|
||||
@ -17,6 +20,10 @@ import Text.Hamlet
|
||||
import Language.Haskell.TH
|
||||
import Language.Haskell.TH.Syntax (Lift)
|
||||
|
||||
import Text.Blaze (preEscapedText)
|
||||
import Text.Blaze.Html.Renderer.Text (renderHtml)
|
||||
import Text.HTML.SanitizeXSS (sanitizeBalance)
|
||||
|
||||
|
||||
data MessageClass = Error | Warning | Info | Success
|
||||
deriving (Eq, Ord, Enum, Bounded, Show, Read, Lift)
|
||||
@ -31,13 +38,47 @@ deriveJSON defaultOptions
|
||||
nullaryPathPiece ''MessageClass camelToPathPiece
|
||||
derivePersistField "MessageClass"
|
||||
|
||||
newtype UnknownMessageClass = UnknownMessageClass Text
|
||||
deriving (Eq, Ord, Read, Show, Generic, Typeable)
|
||||
|
||||
addMessage :: MonadHandler m => MessageClass-> Html -> m ()
|
||||
instance Exception UnknownMessageClass
|
||||
|
||||
|
||||
data Message = Message
|
||||
{ messageClass :: MessageClass
|
||||
, messageContent :: Html
|
||||
}
|
||||
|
||||
instance Eq Message where
|
||||
a == b = ((==) `on` messageClass) a b && ((==) `on` renderHtml . messageContent) a b
|
||||
|
||||
instance Ord Message where
|
||||
a `compare` b = (compare `on` messageClass) a b `mappend` (compare `on` renderHtml . messageContent) a b
|
||||
|
||||
instance ToJSON Message where
|
||||
toJSON Message{..} = object
|
||||
[ "class" .= messageClass
|
||||
, "content" .= renderHtml messageContent
|
||||
]
|
||||
|
||||
instance FromJSON Message where
|
||||
parseJSON = withObject "Message" $ \o -> do
|
||||
messageClass <- o .: "class"
|
||||
messageContent <- preEscapedText . sanitizeBalance <$> o .: "content"
|
||||
return Message{..}
|
||||
|
||||
|
||||
addMessage :: MonadHandler m => MessageClass -> Html -> m ()
|
||||
addMessage mc = ClassyPrelude.Yesod.addMessage (toPathPiece mc)
|
||||
|
||||
addMessageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageClass -> msg -> m ()
|
||||
addMessageI mc = ClassyPrelude.Yesod.addMessageI (toPathPiece mc)
|
||||
|
||||
messageI :: (MonadHandler m, RenderMessage (HandlerSite m) msg) => MessageClass -> msg -> m Message
|
||||
messageI messageClass msg = do
|
||||
messageContent <- toHtml . ($ msg) <$> getMessageRender
|
||||
return Message{..}
|
||||
|
||||
addMessageIHamlet :: ( MonadHandler m
|
||||
, RenderMessage (HandlerSite m) msg
|
||||
, HandlerSite m ~ site
|
||||
@ -46,9 +87,20 @@ addMessageIHamlet mc iHamlet = do
|
||||
mr <- getMessageRender
|
||||
ClassyPrelude.Yesod.addMessage (toPathPiece mc) =<< withUrlRenderer (iHamlet $ toHtml . mr)
|
||||
|
||||
messageIHamlet :: ( MonadHandler m
|
||||
, RenderMessage (HandlerSite m) msg
|
||||
, HandlerSite m ~ site
|
||||
) => MessageClass -> HtmlUrlI18n msg (Route site) -> m Message
|
||||
messageIHamlet mc iHamlet = do
|
||||
mr <- getMessageRender
|
||||
Message mc <$> withUrlRenderer (iHamlet $ toHtml . mr)
|
||||
|
||||
addMessageFile :: MessageClass -> FilePath -> ExpQ
|
||||
addMessageFile mc tPath = [e|addMessageIHamlet mc $(ihamletFile tPath)|]
|
||||
|
||||
messageFile :: MessageClass -> FilePath -> ExpQ
|
||||
messageFile mc tPath = [e|messageIHamlet mc $(ihamletFile tPath)|]
|
||||
|
||||
addMessageWidget :: forall m site.
|
||||
( MonadHandler m
|
||||
, HandlerSite m ~ site
|
||||
@ -58,3 +110,12 @@ addMessageWidget :: forall m site.
|
||||
addMessageWidget mc wgt = do
|
||||
PageContent{pageBody} <- liftHandlerT $ widgetToPageContent wgt
|
||||
addMessageIHamlet mc (const pageBody :: HtmlUrlI18n (SomeMessage site) (Route site))
|
||||
|
||||
messageWidget :: forall m site.
|
||||
( MonadHandler m
|
||||
, HandlerSite m ~ site
|
||||
, Yesod site
|
||||
) => MessageClass -> WidgetT site IO () -> m Message
|
||||
messageWidget mc wgt = do
|
||||
PageContent{pageBody} <- liftHandlerT $ widgetToPageContent wgt
|
||||
messageIHamlet mc (const pageBody :: HtmlUrlI18n (SomeMessage site) (Route site))
|
||||
|
||||
@ -17,10 +17,4 @@ $newline never
|
||||
<script>
|
||||
document.body.classList.remove('no-js');
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Setup body');
|
||||
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body }, bubbles: true, cancelable: true }));
|
||||
});
|
||||
|
||||
^{pageBody pc}
|
||||
|
||||
@ -27,13 +27,13 @@ $if not isModal
|
||||
<!-- prime page actions -->
|
||||
^{pageactionprime}
|
||||
|
||||
<!-- alerts -->
|
||||
<div .alerts>
|
||||
$forall (status, msg) <- mmsgs
|
||||
$with status2 <- bool status "info" (status == "")
|
||||
<div class="alert alert-#{status2}">
|
||||
<div .alert__content>
|
||||
#{msg}
|
||||
<!-- alerts -->
|
||||
<div #alerts .alerts>
|
||||
$forall (status, msg) <- mmsgs
|
||||
$with status2 <- bool status "info" (status == "")
|
||||
<div class="alert alert-#{status2}">
|
||||
<div .alert__content>
|
||||
#{msg}
|
||||
|
||||
<!-- actual content -->
|
||||
^{widget}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
_{MsgHelpIntroduction}
|
||||
|
||||
<form method=post action=@{HelpR} enctype=#{formEnctype}>
|
||||
<form method=post action=@{HelpR} enctype=#{formEnctype} :isModal:data-ajax-submit>
|
||||
^{formWidget}
|
||||
|
||||
@ -5,24 +5,24 @@
|
||||
|
||||
var ALERT_INVISIBLE_CLASS = 'alert--invisible';
|
||||
var TOGGLER_INVISIBLE_CLASS = 'alerts__toggler--visible';
|
||||
var alertsShowingToggler = false;
|
||||
|
||||
window.utils.alerts = function(alertsEl) {
|
||||
|
||||
var alerts = Array.from(alertsEl.querySelectorAll('.alert'));
|
||||
var toggler;
|
||||
var showingToggler = false;
|
||||
var toggler = alertsEl.querySelector('.alerts__toggler');
|
||||
|
||||
function makeToggler() {
|
||||
toggler = document.createElement('DIV');
|
||||
toggler.classList.add('alerts__toggler');
|
||||
toggler.addEventListener('click', function() {
|
||||
alerts.forEach(function(alert) {
|
||||
Array.from(alertsEl.querySelectorAll('.alert')).forEach(function(alert) {
|
||||
alert.classList.remove(ALERT_INVISIBLE_CLASS);
|
||||
toggler.classList.remove(TOGGLER_INVISIBLE_CLASS);
|
||||
});
|
||||
checkToggler();
|
||||
});
|
||||
alertsEl.appendChild(toggler);
|
||||
alertsEl.classList.add('js-initialized');
|
||||
}
|
||||
|
||||
function makeAlert(alertEl) {
|
||||
@ -47,6 +47,8 @@
|
||||
closeAlert(alertEl);
|
||||
}, autoDecay * 1000);
|
||||
}
|
||||
|
||||
alertEl.classList.add('js-initialized');
|
||||
}
|
||||
|
||||
function closeAlert(alertEl) {
|
||||
@ -56,30 +58,42 @@
|
||||
|
||||
function checkToggler() {
|
||||
var hidden = true;
|
||||
alerts.forEach(function(alert) {
|
||||
Array.from(alertsEl.querySelectorAll('.alert')).forEach(function(alert) {
|
||||
if (hidden && !alert.classList.contains(ALERT_INVISIBLE_CLASS)) {
|
||||
hidden = false;
|
||||
}
|
||||
});
|
||||
if (!showingToggler) {
|
||||
showingToggler = true;
|
||||
if (!alertsShowingToggler) {
|
||||
alertsShowingToggler = true;
|
||||
window.setTimeout(function() {
|
||||
toggler.classList.toggle(TOGGLER_INVISIBLE_CLASS, hidden);
|
||||
showingToggler = false;
|
||||
alertsShowingToggler = false;
|
||||
}, 120);
|
||||
}
|
||||
}
|
||||
|
||||
makeToggler();
|
||||
alerts.map(makeAlert);
|
||||
if (!alertsEl.classList.contains('js-initialized') || !toggler)
|
||||
makeToggler();
|
||||
Array.from(alertsEl.querySelectorAll('.alert:not(.js-initialized)')).map(makeAlert);
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
document.addEventListener('setup', function(e) {
|
||||
if (e.detail.module && e.detail.module !== 'alerts')
|
||||
return;
|
||||
|
||||
|
||||
// setup alerts
|
||||
var alertsEl = e.detail.scope.querySelector('.alerts');
|
||||
if (alertsEl)
|
||||
window.utils.alerts(alertsEl);
|
||||
if (e.detail.scope.classList.contains('alerts')) {
|
||||
window.utils.alerts(e.detail.scope);
|
||||
} else {
|
||||
var alertsEl = e.detail.scope.querySelector('.alerts');
|
||||
if (alertsEl)
|
||||
window.utils.alerts(alertsEl);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'alerts' }, bubbles: true, cancelable: true }))
|
||||
});
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
document.addEventListener('setup', function(e) {
|
||||
"use strict";
|
||||
|
||||
if (e.detail.module && e.detail.module !== 'datepicker')
|
||||
return;
|
||||
|
||||
var config = {
|
||||
dtLocal: {
|
||||
@ -34,3 +37,7 @@ document.addEventListener('setup', function(e) {
|
||||
flatpickr(el, config.dtLocal);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'datepicker' }, bubbles: true, cancelable: true }));
|
||||
});
|
||||
|
||||
@ -114,6 +114,8 @@
|
||||
})();
|
||||
|
||||
document.addEventListener('setup', function(e) {
|
||||
if (e.detail.module && e.detail.module !== 'inputs')
|
||||
return;
|
||||
|
||||
// initialize checkboxes
|
||||
Array.from(e.detail.scope.querySelectorAll('input[type="checkbox"]')).forEach(function(inp) {
|
||||
@ -135,3 +137,7 @@ document.addEventListener('setup', function(e) {
|
||||
window.utils.reactiveFileCheckbox(inp);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'inputs' }, bubbles: true, cancelable: true }));
|
||||
});
|
||||
|
||||
@ -87,6 +87,25 @@ input[type*="time"] {
|
||||
font-family: var(--font-base);
|
||||
line-height: 1.5;
|
||||
padding: 4px 13px;
|
||||
|
||||
&:focus {
|
||||
border-color: #3273dc;
|
||||
box-shadow: 0 0 0 0.125em rgba(50,115,220,.25);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
background-color: #f5f5f5;
|
||||
color: #7a7a7a;
|
||||
box-shadow: none;
|
||||
border-color: #dbdbdb;
|
||||
}
|
||||
|
||||
&[readonly] {
|
||||
background-color: #f5f5f5;
|
||||
box-shadow: none;
|
||||
border-color: #dbdbdb;
|
||||
}
|
||||
}
|
||||
|
||||
input[type="number"] {
|
||||
@ -100,19 +119,6 @@ input[type*="time"],
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="password"]:focus,
|
||||
input[type="url"]:focus,
|
||||
input[type="number"]:focus,
|
||||
input[type="email"]:focus {
|
||||
/* border-bottom-color: var(--color-light);
|
||||
background-color: transparent;
|
||||
box-shadow: 0 0 13px var(--color-lighter); */
|
||||
border-color: #3273dc;
|
||||
box-shadow: 0 0 0 0.125em rgba(50,115,220,.25);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* BUTTON STYLE SEE default-layout.lucius */
|
||||
|
||||
/* TEXTAREAS */
|
||||
@ -132,12 +138,25 @@ textarea {
|
||||
border-radius: 2px;
|
||||
box-shadow: inset 0 1px 2px 1px rgba(50,50,50,.05);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
border-color: #3273dc;
|
||||
box-shadow: 0 0 0 0.125em rgba(50,115,220,.25);
|
||||
outline: 0;
|
||||
&:focus {
|
||||
border-color: #3273dc;
|
||||
box-shadow: 0 0 0 0.125em rgba(50,115,220,.25);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
background-color: #f3f3f3;
|
||||
color: #7a7a7a;
|
||||
box-shadow: none;
|
||||
border-color: #dbdbdb;
|
||||
}
|
||||
|
||||
&[readonly] {
|
||||
background-color: #f5f5f5;
|
||||
box-shadow: none;
|
||||
border-color: #dbdbdb;
|
||||
}
|
||||
}
|
||||
|
||||
/* OPTIONS */
|
||||
|
||||
@ -10,6 +10,10 @@
|
||||
// var origParent = modal.parentNode;
|
||||
|
||||
function open(event) {
|
||||
if (!modal.classList.contains('js-modal-initialized')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// disable modals for narrow screens
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
@ -17,7 +21,7 @@
|
||||
modal.classList.add('modal--open');
|
||||
overlay.classList.add('modal__overlay');
|
||||
// document.body.insertBefore(modal, null);
|
||||
document.body.insertBefore(overlay, modal);
|
||||
document.body.insertBefore(overlay, modal);
|
||||
overlay.classList.add('modal__overlay--open');
|
||||
|
||||
if ('closeable' in modal.dataset) {
|
||||
@ -37,58 +41,39 @@
|
||||
}
|
||||
|
||||
function close(event) {
|
||||
if (typeof event === 'undefined' || event.target === closer || event.target === overlay) {
|
||||
overlay.remove();
|
||||
// origParent.insertBefore(modal, null);
|
||||
modal.classList.remove('modal--open');
|
||||
closer.removeEventListener('click', close, false);
|
||||
}
|
||||
overlay.remove();
|
||||
// origParent.insertBefore(modal, null);
|
||||
modal.classList.remove('modal--open');
|
||||
closer.removeEventListener('click', close, false);
|
||||
};
|
||||
|
||||
function setup() {
|
||||
document.body.insertBefore(modal, null);
|
||||
document.body.insertBefore(modal, null);
|
||||
|
||||
// every modal can be openend via document-wide event, see openOnEvent
|
||||
document.addEventListener('modal-open', openOnEvent, false);
|
||||
// if modal has trigger assigned to it open modal on click
|
||||
if (trigger) {
|
||||
trigger.classList.add('modal__trigger');
|
||||
trigger.addEventListener('click', open, false);
|
||||
}
|
||||
|
||||
|
||||
if ('dynamic' in modal.dataset) {
|
||||
var dynamicContentURL = trigger.getAttribute('href');
|
||||
|
||||
var i = dynamicContentURL.indexOf('?');
|
||||
if (i === -1) {
|
||||
dynamicContentURL = dynamicContentURL + "?" + #{String modalParameter};
|
||||
} else {
|
||||
dynamicContentURL = dynamicContentURL.slice(0,i) + "?" + #{String modalParameter} + "&" + dynamicContentURL.slice(i + 1);
|
||||
}
|
||||
console.log(dynamicContentURL);
|
||||
|
||||
if (dynamicContentURL.length > 0) {
|
||||
fetch(dynamicContentURL, {
|
||||
credentials: 'same-origin',
|
||||
}).then(function(response) {
|
||||
return response.text();
|
||||
}).then(function(body) {
|
||||
function fetchModal(url, init) {
|
||||
function responseHtml(body) {
|
||||
var modalContent = document.createElement('div');
|
||||
modalContent.classList.add('modal__content');
|
||||
modalContent.innerHTML = body;
|
||||
|
||||
var contentBody = modalContent.querySelector('.main__content-body');
|
||||
var scriptTags = [];
|
||||
if (contentBody) {
|
||||
modalContent.querySelectorAll('script').forEach(function(scriptTag) {
|
||||
if (Array.from(document.body.querySelectorAll('script')).some(function(haystack) {
|
||||
var existsAlready = Array.from(document.body.querySelectorAll('script')).some(function(haystack) {
|
||||
if (haystack.text === scriptTag.text && haystack.getAttribute('src') === scriptTag.getAttribute('src')) {
|
||||
scriptTags.push(haystack);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
})) { return }
|
||||
});
|
||||
if (existsAlready)
|
||||
return;
|
||||
|
||||
console.log(scriptTag);
|
||||
var scriptClone = document.createElement('script');
|
||||
if (scriptTag.text)
|
||||
scriptClone.text = striptTag.text;
|
||||
@ -122,9 +107,25 @@
|
||||
|
||||
document.head.insertBefore(linkTag, null);
|
||||
});
|
||||
|
||||
var modalAlertsEl = modalContent.querySelector('#alerts');
|
||||
var alertsEl = document.body.querySelector('#alerts');
|
||||
if (alertsEl && modalAlertsEl) {
|
||||
var modalAlerts = Array.from(modalAlertsEl.childNodes);
|
||||
|
||||
modalAlerts.forEach(function(alertEl) {
|
||||
alertsEl.insertBefore(alertEl, alertsEl.querySelector('.alerts__toggler'));
|
||||
});
|
||||
|
||||
if (modalAlerts.length !== 0)
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: alertsEl } }));
|
||||
|
||||
contentBody.removeChild(modalAlertsEl);
|
||||
}
|
||||
|
||||
modalContent = contentBody;
|
||||
}
|
||||
modalContent.classList.add('modal__content');
|
||||
|
||||
var nudgeAttr = function(attr, x) {
|
||||
var oldVal = x.getAttribute(attr);
|
||||
@ -139,36 +140,161 @@
|
||||
modalContent.querySelectorAll('[' + attr + ']').forEach(function(x) { nudgeAttr(attr, x); });
|
||||
});
|
||||
|
||||
modal.appendChild(modalContent);
|
||||
modal.querySelectorAll('.modal__content').forEach(function(prev) { modal.removeChild(prev); });
|
||||
modal.insertBefore(modalContent, null);
|
||||
|
||||
var triggerContentLoad = function() {
|
||||
console.log('contentReady', modal);
|
||||
|
||||
document.dispatchEvent(new CustomEvent('setup', {
|
||||
detail: { scope: modalContent },
|
||||
detail: { scope: modal },
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
}));
|
||||
}
|
||||
|
||||
if (scriptTags.length !== 0) {
|
||||
scriptTags.forEach(function(t) { t.onload = triggerContentLoad; });
|
||||
scriptTags.forEach(function(t) { t.addEventListener('load', triggerContentLoad); });
|
||||
triggerContentLoad();
|
||||
|
||||
return 'html';
|
||||
}
|
||||
|
||||
function responseJson(data) {
|
||||
var alertsEl = document.querySelector('#alerts');
|
||||
if (!alertsEl)
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var alert = document.createElement('div');
|
||||
alert.classList.add('alert', 'alert-' + data[i].class);
|
||||
var alertContent = document.createElement('div');
|
||||
alertContent.classList.add('alert__content');
|
||||
alertContent.innerHTML = data[i].content;
|
||||
alert.appendChild(alertContent);
|
||||
|
||||
alertsEl.insertBefore(alert, alertsEl.querySelector('.alerts__toggler'));
|
||||
}
|
||||
|
||||
triggerContentLoad();
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: alertsEl }, bubbles: true, cancelable: true }));
|
||||
|
||||
return 'json';
|
||||
}
|
||||
|
||||
|
||||
return fetch(url, init).then(function(response) {
|
||||
var contentType = response.headers.get('Content-Type')
|
||||
if (contentType && contentType.includes('text/html')) {
|
||||
return response.text().then(responseHtml);
|
||||
} else if (contentType && contentType.includes('application/json')) {
|
||||
return response.json().then(responseJson);
|
||||
} else {
|
||||
console.log(response);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
modal.addEventListener('modal-fetch', function(event) {
|
||||
var dynamicContentURL = (event.detail && event.detail.url) || trigger.getAttribute('href');
|
||||
|
||||
var fetchInit = (event.detail && event.detail.init) || {
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
#{String (toPathPiece HeaderIsModal)}: 'True'
|
||||
}
|
||||
};
|
||||
|
||||
if (dynamicContentURL.length > 0) {
|
||||
fetchModal(dynamicContentURL, fetchInit).then((event.detail && event.detail.then) || (function(){}));
|
||||
}
|
||||
});
|
||||
modal.dispatchEvent(new CustomEvent('modal-fetch', {
|
||||
detail: {
|
||||
then: (function() {
|
||||
if (!trigger)
|
||||
return;
|
||||
|
||||
trigger.classList.add('modal__trigger');
|
||||
trigger.addEventListener('click', open, false);
|
||||
})
|
||||
}
|
||||
}));
|
||||
} else if (trigger) { // if modal has trigger assigned to it open modal on click
|
||||
trigger.classList.add('modal__trigger');
|
||||
trigger.addEventListener('click', open, false);
|
||||
}
|
||||
|
||||
|
||||
// tell further modals, that this one already got initialized
|
||||
modal.classList.add('js-modal-initialized');
|
||||
|
||||
modal.addEventListener('modal-close', close);
|
||||
}
|
||||
|
||||
setup();
|
||||
};
|
||||
|
||||
window.utils.ajaxSubmit = function(modal, form) {
|
||||
function doSubmit(event) {
|
||||
event.preventDefault();
|
||||
|
||||
var modalContent = modal.querySelector('.modal__content');
|
||||
modalContent.style.pointerEvents = 'none';
|
||||
modalContent.style.opacity = 0.5;
|
||||
|
||||
modal.dispatchEvent(new CustomEvent('modal-fetch', {
|
||||
detail: {
|
||||
url: form.target,
|
||||
init: {
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
#{String (toPathPiece HeaderIsModal)}: 'True'
|
||||
},
|
||||
method: form.method,
|
||||
body: new FormData(form)
|
||||
},
|
||||
then: (function(typ) {
|
||||
modal.dispatchEvent(new CustomEvent('modal-close'));
|
||||
|
||||
modalContent.style.pointerEvents = 'auto';
|
||||
modalContent.style.opacity = 1;
|
||||
|
||||
if (typ === 'json') {
|
||||
modal.dispatchEvent(new CustomEvent('modal-fetch'));
|
||||
}
|
||||
})
|
||||
},
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
}));
|
||||
};
|
||||
|
||||
form.addEventListener('submit', doSubmit);
|
||||
form.classList.add('js-ajax-initialized');
|
||||
};
|
||||
})();
|
||||
|
||||
document.addEventListener('setup', function(e) {
|
||||
if (e.detail.module && e.detail.module !== 'modal')
|
||||
return;
|
||||
|
||||
Array.from(e.detail.scope.querySelectorAll('.js-modal:not(.js-modal-initialized)')).map(function(modal) {
|
||||
new utils.modal(modal);
|
||||
Array.from(e.detail.scope.querySelectorAll('.js-modal:not(.js-modal-initialized)')).forEach(function(modal) {
|
||||
window.utils.modal(modal);
|
||||
});
|
||||
|
||||
if (e.detail.scope.classList.contains('js-modal')) {
|
||||
Array.from(e.detail.scope.querySelectorAll('form[data-ajax-submit]:not(.js-ajax-initialized)')).forEach(function(form) {
|
||||
window.utils.ajaxSubmit(e.detail.scope, form);
|
||||
});
|
||||
} else {
|
||||
Array.from(e.detail.scope.querySelectorAll('.js-modal')).map(function(modal) {
|
||||
Array.from(modal.querySelectorAll('form[data-ajax-submit]:not(.js-ajax-initialized)')).forEach(function(form) {
|
||||
window.utils.ajaxSubmit(modal, form);
|
||||
});
|
||||
});
|
||||
};
|
||||
}, false);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'modal' }, bubbles: true, cancelable: true }))
|
||||
});
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
div.modal {
|
||||
.modal {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
@ -11,8 +11,7 @@ div.modal {
|
||||
border-radius: 2px;
|
||||
z-index: -1;
|
||||
color: var(--color-font);
|
||||
padding: 20px;
|
||||
padding-right: 65px;
|
||||
padding: 0 65px 0 20px;
|
||||
overflow: auto;
|
||||
transition: all .15s ease;
|
||||
pointer-events: none;
|
||||
@ -24,6 +23,10 @@ div.modal {
|
||||
z-index: 200;
|
||||
transform: translate(-50%, -50%) scale(1, 1);
|
||||
}
|
||||
|
||||
.modal__content {
|
||||
margin: 20px 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
*/
|
||||
|
||||
document.addEventListener('setup', function(e) {
|
||||
if (e.detail.module && e.detail.module !== 'showHide')
|
||||
return;
|
||||
|
||||
var LSNAME = 'SHOW_HIDE';
|
||||
|
||||
@ -50,3 +52,7 @@ document.addEventListener('setup', function(e) {
|
||||
addEventHandler(el);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'showHide' }, bubbles: true, cancelable: true }))
|
||||
});
|
||||
|
||||
@ -53,7 +53,6 @@
|
||||
})();
|
||||
|
||||
document.addEventListener('setup', function(e) {
|
||||
|
||||
// JS-TOOLTIPS NOT USED CURRENTLY.
|
||||
|
||||
// initialize tooltips set via `data-tooltip`
|
||||
|
||||
@ -6,6 +6,9 @@
|
||||
function setupAsync(wrapper) {
|
||||
|
||||
var table = wrapper.querySelector('#' + #{String $ dbtIdent});
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
var ths = Array.from(table.querySelectorAll('th.sortable'));
|
||||
var pagination = wrapper.querySelector('#' + #{String $ dbtIdent} + '-pagination');
|
||||
|
||||
|
||||
@ -18,9 +18,15 @@
|
||||
})();
|
||||
|
||||
document.addEventListener('setup', function(e) {
|
||||
if (e.detail.module && e.detail.module !== 'asidenav')
|
||||
return;
|
||||
|
||||
var asidenavEl = e.detail.scope.querySelector('.main__aside');
|
||||
|
||||
window.utils.aside(asidenavEl);
|
||||
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'asidenav' }, bubbles: true, cancelable: true }))
|
||||
});
|
||||
|
||||
@ -69,6 +69,8 @@
|
||||
})();
|
||||
|
||||
document.addEventListener('setup', function(e) {
|
||||
if (e.detail.module && e.detail.module !== 'showHide')
|
||||
return;
|
||||
|
||||
console.log('form setup', e.detail.scope);
|
||||
|
||||
@ -95,5 +97,8 @@ document.addEventListener('setup', function(e) {
|
||||
});
|
||||
return done;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.dispatchEvent(new CustomEvent('setup', { detail: { scope: document.body, module: 'showHide' }, bubbles: true, cancelable: true }))
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<div .modal.js-modal #modal-#{modalId} data-trigger=#{triggerId} data-closeable :modalDynamic:data-dynamic>
|
||||
$case modalContent
|
||||
$of Right content
|
||||
^{content}
|
||||
<div .modal__content>
|
||||
^{content}
|
||||
$of Left _
|
||||
|
||||
@ -17,7 +17,7 @@ $newline never
|
||||
$of NavbarAside
|
||||
<li .navbar__list-item :highlight (urlRoute menuItemRoute):.navbar__list-item--active>
|
||||
$if menuItemModal
|
||||
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable=true data-dynamic=True>
|
||||
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable data-dynamic>
|
||||
<a .navbar__link-wrapper href=#{route} ##{menuIdent}>
|
||||
<i .fas.fa-#{fromMaybe "none" menuItemIcon}>
|
||||
<div .navbar__link-label>_{SomeMessage menuItemLabel}
|
||||
@ -29,14 +29,14 @@ $newline never
|
||||
$of NavbarRight
|
||||
<li .navbar__list-item :highlight (urlRoute menuItemRoute):.navbar__list-item--active>
|
||||
$if menuItemModal
|
||||
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable=true data-dynamic=True>
|
||||
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable data-dynamic>
|
||||
<a .navbar__link-wrapper href=#{route} ##{menuIdent}>
|
||||
<i .fas.fa-#{fromMaybe "none" menuItemIcon}>
|
||||
<div .navbar__link-label>_{SomeMessage menuItemLabel}
|
||||
$of NavbarSecondary
|
||||
<li .navbar__list-item.navbar__list-item--secondary :highlight (urlRoute menuItemRoute):.navbar__list-item--active>
|
||||
$if menuItemModal
|
||||
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable=true data-dynamic=True>
|
||||
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable data-dynamic>
|
||||
<a .navbar__link-wrapper href=#{route} ##{menuIdent}>
|
||||
<i .fas.fa-#{fromMaybe "none" menuItemIcon}>
|
||||
<div .navbar__link-label>_{SomeMessage menuItemLabel}
|
||||
|
||||
@ -7,11 +7,11 @@ $if hasPageActions
|
||||
$of PageActionPrime
|
||||
<li .pagenav__list-item>
|
||||
$if menuItemModal
|
||||
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable=true data-dynamic=True>
|
||||
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable data-dynamic>
|
||||
<a .pagenav__link-wrapper href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel}
|
||||
$of PageActionSecondary
|
||||
<li .pagenav__list-item>
|
||||
$if menuItemModal
|
||||
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable=true data-dynamic=True>
|
||||
<div .modal.js-modal #modal-#{menuIdent} data-trigger=#{menuIdent} data-closeable data-dynamic>
|
||||
<a .pagenav__link-wrapper href=#{route} ##{menuIdent}>_{SomeMessage menuItemLabel}
|
||||
$of _
|
||||
|
||||
Loading…
Reference in New Issue
Block a user