From 7b5337723d6dd300dc18a0881c589f89dea0bdbf Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Thu, 23 Apr 2020 16:52:34 +0200 Subject: [PATCH] feat(faqs): initial --- frontend/src/app.sass | 48 ++++++++++++-- frontend/src/utils/show-hide/show-hide.js | 33 ++++++++-- frontend/src/utils/show-hide/show-hide.sass | 4 +- messages/faq/de-de-formal.msg | 3 + messages/uniworx/de-de-formal.msg | 7 +- routes | 1 + src/Foundation.hs | 20 ++++++ src/Handler/Help.hs | 4 ++ src/Handler/Info.hs | 66 +++++++++++++++++++ src/Handler/Info/TH.hs | 50 ++++++++++++++ templates/faq.hamlet | 10 +++ templates/help.hamlet | 7 ++ .../faq/campus-cant-login.de-de-formal.hamlet | 30 +++++++++ .../forgotten-password.de-de-formal.hamlet | 12 ++++ .../faq/no-campus-account.de-de-formal.hamlet | 35 ++++++++++ 15 files changed, 316 insertions(+), 14 deletions(-) create mode 100644 messages/faq/de-de-formal.msg create mode 100644 templates/faq.hamlet create mode 100644 templates/i18n/faq/campus-cant-login.de-de-formal.hamlet create mode 100644 templates/i18n/faq/forgotten-password.de-de-formal.hamlet create mode 100644 templates/i18n/faq/no-campus-account.de-de-formal.hamlet diff --git a/frontend/src/app.sass b/frontend/src/app.sass index 3b40ba70e..c25d04128 100644 --- a/frontend/src/app.sass +++ b/frontend/src/app.sass @@ -914,11 +914,11 @@ th, td dt, .dt font-weight: 600 - &.sec - font-style: italic - font-size: 0.9rem - font-weight: 600 - color: var(--color-fontsec) + &.sec + font-style: italic + font-size: 0.9rem + font-weight: 600 + color: var(--color-fontsec) dd, .dd margin-left: 12px @@ -926,6 +926,12 @@ th, td dd + dt, .dd + dt, dd + .dt, .dd + .dt margin-top: 17px +.deflist--no-grid + dt, .dt + font-weight: 600 + dd, .dd + margin-left: 12px + .explanation font-style: italic font-size: 0.9rem @@ -1320,3 +1326,35 @@ a.breadcrumbs__home &--success border-left-color: var(--color-success) + +.faq__question + font-size: 20px + font-weight: 600 + + margin: 0 + +.faq__answer + margin-left: 17px + +:not(.show-hide--collapsed) > .faq__answer + margin-top: 7px + +.faq__section + padding-bottom: 17px + + &:last-child, &.show-hide--collapsed + border-bottom: none + padding-bottom: 0 + + & + section:not(.faq__section) + border-top: 1px solid #d3d3d3 + padding-top: 30px + +.faq__section + .faq__section + margin-top: 17px + +.faq__question-link + opacity: 0.2 + + &:hover + opacity: 1 diff --git a/frontend/src/utils/show-hide/show-hide.js b/frontend/src/utils/show-hide/show-hide.js index 4da5f8c06..3419ee1f4 100644 --- a/frontend/src/utils/show-hide/show-hide.js +++ b/frontend/src/utils/show-hide/show-hide.js @@ -57,23 +57,44 @@ export class ShowHide { this._element.classList.add(SHOW_HIDE_TOGGLE_RIGHT_CLASS); } + this._checkHash(); + + window.addEventListener('hashchange', this._checkHash.bind(this)); + // mark as initialized this._element.classList.add(SHOW_HIDE_INITIALIZED_CLASS, SHOW_HIDE_TOGGLE_CLASS); } - destroy() { - this._element.removeEventListener('click', this._clickHandler); - } + destroy() {} _addClickListener() { - this._element.addEventListener('click', this._clickHandler); + this._element.addEventListener('click', this._clickHandler.bind(this)); } - _clickHandler = () => { - const newState = this._element.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS); + _show() { + this._element.parentElement.classList.remove(SHOW_HIDE_COLLAPSED_CLASS); + } + + _toggle() { + return this._element.parentElement.classList.toggle(SHOW_HIDE_COLLAPSED_CLASS); + } + + _clickHandler(event) { + if (event.target.closest('a') && event.target.closest('a') !== this._element) + return; + if (event.target.matches('a') && event.target !== this._element) + return; + + const newState = this._toggle(); if (this._showHideId) { this._storageManager.save(this._showHideId, newState); } } + + _checkHash() { + if (this._element.id && '#' + this._element.id === location.hash) { + this._show(); + } + } } diff --git a/frontend/src/utils/show-hide/show-hide.sass b/frontend/src/utils/show-hide/show-hide.sass index cb5c922f2..9609ac0a5 100644 --- a/frontend/src/utils/show-hide/show-hide.sass +++ b/frontend/src/utils/show-hide/show-hide.sass @@ -19,7 +19,7 @@ $show-hide-toggle-size: 6px border-right: 2px solid currentColor border-top: 2px solid currentColor transition: transform .2s ease - transform: translateY(-50%) rotate(-45deg) + transform: translateY(2px) translateY(-50%) rotate(-45deg) @media (max-width: 768px) left: auto @@ -33,7 +33,7 @@ $show-hide-toggle-size: 6px .show-hide--collapsed .show-hide__toggle::before - transform: translateY(-50%) rotate(135deg) + transform: translateY(-2px) translateY(-50%) rotate(135deg) & > :not(.show-hide__toggle) display: block diff --git a/messages/faq/de-de-formal.msg b/messages/faq/de-de-formal.msg new file mode 100644 index 000000000..5491fec9f --- /dev/null +++ b/messages/faq/de-de-formal.msg @@ -0,0 +1,3 @@ +FAQNoCampusAccount: Ich habe keinen LMU Campus-Login; kann ich trotzdem Zugang zum System erhalten? +FAQForgottenPassword: Ich habe mein Passwort vergessen +FAQCampusCantLogin: Ich kann mich mit meinem LMU Campus-Login nicht anmelden \ No newline at end of file diff --git a/messages/uniworx/de-de-formal.msg b/messages/uniworx/de-de-formal.msg index ed5d44317..f7548e2af 100644 --- a/messages/uniworx/de-de-formal.msg +++ b/messages/uniworx/de-de-formal.msg @@ -1250,6 +1250,7 @@ MenuAllocationUsers: Bewerber MenuAllocationPriorities: Zentrale Dringlichkeiten MenuAllocationCompute: Platzvergabe berechnen MenuAllocationAccept: Platzvergabe akzeptieren +MenuFaq: FAQ BreadcrumbSubmissionFile: Datei BreadcrumbSubmissionUserInvite: Einladung zur Abgabe @@ -1320,6 +1321,7 @@ BreadcrumbAllocationPriorities: Zentrale Dringlichkeiten BreadcrumbAllocationCompute: Platzvergabe berechnen BreadcrumbAllocationAccept: Platzvergabe akzeptieren BreadcrumbMessageHide: Verstecken +BreadcrumbFaq: FAQ ExternalExamEdit coursen@CourseName examn@ExamName: Bearbeiten: #{coursen}, #{examn} ExternalExamGrades coursen@CourseName examn@ExamName: Prüfungsleistungen: #{coursen}, #{examn} @@ -2478,4 +2480,7 @@ BearerTokenOverrideExpiration: Ablaufzeitpunkt überschreiben BearerTokenExpires: Ablaufzeitpunkt BearerTokenExpiresTip: Wird der Ablaufzeitpunkt überschrieben und kein Ablaufzeitpunkt angegeben, ist das Token für immer gültig. BearerTokenOverrideStart: Startzeitpunkt -BearerTokenOverrideStartTip: Wird kein Startzeitpunkt angegeben, wird bei Verwendung des Tokens nur der Ablaufzeitpunkt überprüft. \ No newline at end of file +BearerTokenOverrideStartTip: Wird kein Startzeitpunkt angegeben, wird bei Verwendung des Tokens nur der Ablaufzeitpunkt überprüft. + +FaqTitle: Häufig gestellte Fragen +AdditionalFaqs: Weitere häufig gestellte Fragen \ No newline at end of file diff --git a/routes b/routes index eb115bc59..f065074cf 100644 --- a/routes +++ b/routes @@ -64,6 +64,7 @@ /info/legal LegalR GET !free /info/allocation InfoAllocationR GET !free /info/glossary GlossaryR GET !free +/info/faq FaqR GET !free /version VersionR GET !free /help HelpR GET POST !free diff --git a/src/Foundation.hs b/src/Foundation.hs index 35f129223..3c7edc2d6 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -2198,6 +2198,7 @@ instance YesodBreadcrumbs UniWorX where breadcrumb LegalR = i18nCrumb MsgMenuLegal $ Just InfoR breadcrumb InfoAllocationR = i18nCrumb MsgBreadcrumbAllocationInfo $ Just InfoR breadcrumb VersionR = i18nCrumb MsgMenuVersion $ Just InfoR + breadcrumb FaqR = i18nCrumb MsgBreadcrumbFaq $ Just InfoR breadcrumb HelpR = i18nCrumb MsgMenuHelp Nothing @@ -2550,6 +2551,14 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navQuick' = mempty , navForceActive = False } + , return $ NavFooter NavLink + { navLabel = MsgMenuFaq + , navRoute = FaqR + , navAccess' = return True + , navType = NavTypeLink { navModal = False } + , navQuick' = mempty + , navForceActive = False + } , return $ NavFooter NavLink { navLabel = MsgMenuGlossary , navRoute = GlossaryR @@ -3126,6 +3135,17 @@ pageActions InstanceR = return ] pageActions HelpR = return [ NavPageActionPrimary + { navLink = NavLink + { navLabel = MsgMenuFaq + , navRoute = FaqR + , navAccess' = return True + , navType = NavTypeLink { navModal = False } + , navQuick' = mempty + , navForceActive = False + } + , navChildren = [] + } + , NavPageActionPrimary { navLink = NavLink { navLabel = MsgInfoLecturerTitle , navRoute = InfoLecturerR diff --git a/src/Handler/Help.hs b/src/Handler/Help.hs index 94c769b08..098695563 100644 --- a/src/Handler/Help.hs +++ b/src/Handler/Help.hs @@ -2,6 +2,7 @@ module Handler.Help where import Import import Handler.Utils +import Handler.Info (faqsWidget) import Jobs import qualified Data.Map as Map @@ -68,4 +69,7 @@ postHelpR = do , formEncoding = formEnctype , formAttrs = [ asyncSubmitAttr | isModal ] } + + mFaqs <- (>>= \(mWgt, truncated) -> (, truncated) <$> mWgt) <$> traverse (faqsWidget $ Just 5) (Just <$> mReferer) + $(widgetFile "help") diff --git a/src/Handler/Info.hs b/src/Handler/Info.hs index dfc39e730..36d8657d3 100644 --- a/src/Handler/Info.hs +++ b/src/Handler/Info.hs @@ -85,3 +85,69 @@ getGlossaryR = where entries = $(i18nWidgetFiles "glossary") msgMap = $(glossaryTerms "glossary") + + +mkFaqItems "faq" +mkMessageFor "UniWorX" "FAQItem" "messages/faq" "de-de-formal" + +faqsWidget :: ( MonadHandler m, HandlerSite m ~ UniWorX ) + => Maybe Natural -> Maybe (Route UniWorX) -> m (Maybe Widget, Bool) +faqsWidget mLimit route = do + faqs <- for route $ \route' -> filterM (showFAQ route') universeF + MsgRenderer mr <- getMsgRenderer + let + rItems' = sortOn (CI.mk . views _1 mr) $ do + (k, wgt) <- Map.toList items + msg <- maybeToList $ Map.lookup k faqItemMap + whenIsJust faqs $ \faqs' -> + guard $ msg `elem` faqs' + return (msg, wgt) + + rItems <- case (,) <$> route <*> mLimit of + Nothing -> return rItems' + Just (route', limit) -> do + let wIndices = zip [0..] rItems' + wPrios <- forM wIndices $ \x@(_, (msg, _)) -> (, x) . Just <$> prioFAQ route' msg + let prioLimited = go Nothing [] $ sortOn (views _1 Down) wPrios + where + go _ acc [] = acc + go maxP acc ((p, x) : xs) + | maxP == Just p || length acc < fromIntegral limit + = go (Just p) (x : acc) xs + | otherwise + = acc + return . map (view _2) $ sortOn (view _1) prioLimited + + let truncated = length rItems < length rItems' + + return ( guardOn (not $ null rItems') $(widgetFile "faq") + , truncated + ) + where + items = $(i18nWidgetFiles "faq") + + faqLink :: FAQItem -> Widget + faqLink = toWidget <=< toTextUrl . (FaqR :#:) + + +getFaqR :: Handler Html +getFaqR = + siteLayoutMsg' MsgFaqTitle $ do + setTitleI MsgFaqTitle + + fromMaybe mempty . view _1 =<< faqsWidget Nothing Nothing + +showFAQ :: ( MonadHandler m, HandlerSite m ~ UniWorX ) + => Route UniWorX -> FAQItem -> m Bool +showFAQ _ FAQNoCampusAccount = is _Nothing <$> maybeAuthId +showFAQ (AuthR _) FAQCampusCantLogin = return True +showFAQ _ FAQCampusCantLogin = is _Nothing <$> maybeAuthId +showFAQ (AuthR _) FAQForgottenPassword = return True +showFAQ _ FAQForgottenPassword = is _Nothing <$> maybeAuthId +-- showFAQ _ _ = return False + +prioFAQ :: Monad m + => Route UniWorX -> FAQItem -> m Rational +prioFAQ _ FAQNoCampusAccount = return 1 +prioFAQ _ FAQCampusCantLogin = return 1 +prioFAQ _ FAQForgottenPassword = return 1 diff --git a/src/Handler/Info/TH.hs b/src/Handler/Info/TH.hs index 25c55bdb6..a36694c5c 100644 --- a/src/Handler/Info/TH.hs +++ b/src/Handler/Info/TH.hs @@ -1,5 +1,6 @@ module Handler.Info.TH ( glossaryTerms + , mkFaqItems ) where import Import @@ -21,3 +22,52 @@ glossaryTerms basename = do where unPathPiece :: Text -> String unPathPiece = repack . mconcat . map (over _head Char.toUpper) . Text.splitOn "-" + +mkFaqItems :: FilePath -> DecsQ +mkFaqItems basename = do + itemsAvailable <- i18nWidgetFilesAvailable' basename + let items = Map.mapWithKey (\k _ -> "FAQ" <> unPathPiece k) itemsAvailable + sequence + [ dataD (cxt []) dataName [] Nothing + [ normalC (mkName conName) [] + | (_, conName) <- Map.toAscList items + ] + [ derivClause (Just StockStrategy) + [ conT ''Eq + , conT ''Ord + , conT ''Read + , conT ''Show + , conT ''Enum + , conT ''Bounded + , conT ''Generic + , conT ''Typeable + ] + , derivClause (Just AnyclassStrategy) + [ conT ''Universe + , conT ''Finite + ] + ] + , instanceD (cxt []) (conT ''PathPiece `appT` conT dataName) + [ funD 'toPathPiece + [ clause [conP (mkName con) []] (normalB . litE . stringL $ repack int) [] + | (int, con) <- Map.toList items + ] + , funD 'fromPathPiece + [ clause [varP $ mkName "t"] + ( guardedB + [ (,) <$> normalG [e|$(varE $ mkName "t") == int|] <*> [e|Just $(conE $ mkName con)|] + | (int, con) <- Map.toList items + ]) [] + , clause [wildP] (normalB [e|Nothing|]) [] + ] + ] + , sigD (mkName "faqItemMap") [t|Map Text $(conT dataName)|] + , funD (mkName "faqItemMap") + [ clause [] (normalB [e| Map.fromList $(listE . map (\(int, con) -> tupE [litE . stringL $ repack int, conE $ mkName con]) $ Map.toList items) |]) [] + ] + ] + where + unPathPiece :: Text -> String + unPathPiece = repack . mconcat . map (over _head Char.toUpper) . Text.splitOn "-" + + dataName = mkName "FAQItem" diff --git a/templates/faq.hamlet b/templates/faq.hamlet new file mode 100644 index 000000000..d621c1768 --- /dev/null +++ b/templates/faq.hamlet @@ -0,0 +1,10 @@ +$newline never +$forall (rItem, wgt) <- rItems +
+

+ _{rItem} + \ # + + +
+ ^{wgt} diff --git a/templates/help.hamlet b/templates/help.hamlet index 4b09217cb..efa4db891 100644 --- a/templates/help.hamlet +++ b/templates/help.hamlet @@ -1,4 +1,11 @@
_{MsgHelpIntroduction} +$maybe (faqs, truncated) <- mFaqs + ^{faqs} + $if truncated +
+

+ + _{MsgAdditionalFaqs}
^{formWidget} diff --git a/templates/i18n/faq/campus-cant-login.de-de-formal.hamlet b/templates/i18n/faq/campus-cant-login.de-de-formal.hamlet new file mode 100644 index 000000000..9caa83b79 --- /dev/null +++ b/templates/i18n/faq/campus-cant-login.de-de-formal.hamlet @@ -0,0 +1,30 @@ +$newline never + +

+ Können sie sich mit exakt identischen (idealerweise # + copy&paste) Daten # + im Campus-Portal # + anmelden?
+ + Falls nicht ist davon auszugehen, dass Sie Ihre Anmeldedaten falsch # + eingeben oder keinen # + Campus-Login besitzen. + +

+ Beachten Sie dabei auch, dass Uni2work Leerzeichen sowohl im # + Passwort als auch bei der Kennung berücksichtigt.
+ + Beim Passwort ist zudem Groß- und Kleinschreibung relevant. + +

+ Uni2work bietet zwei Login-Formulare.
+ + Für die Anmeldung mit der LMU Campus-Kennung müssen Sie das Formular # + „Campus-Login“ verwenden. + +

+ Falls Sie sich # + im Campus-Portal # + anmelden können, aber nicht in Uni2work, wenden Sie sich bitte über # + das Hilfe-Formular, oben rechts auf jeder # + Seite, an die Uni2work-Administration. diff --git a/templates/i18n/faq/forgotten-password.de-de-formal.hamlet b/templates/i18n/faq/forgotten-password.de-de-formal.hamlet new file mode 100644 index 000000000..27001dcbc --- /dev/null +++ b/templates/i18n/faq/forgotten-password.de-de-formal.hamlet @@ -0,0 +1,12 @@ +$newline never + +

+ Wenn Sie sich gewöhnlicherweise mit Ihrem LMU Campus-Login anmelden, # + wenden Sie sich bitte an # + den IT-Servicedesk # + um Ihr Passwort zurücksetzen zu lassen. + +

+ Wenn Sie sich mit einer Uni2work-internen Kennung anmelden wenden # + Sie sich dafür bitte über das Hilfe-Formular, # + oben rechts auf jeder Seite, an die Uni2work-Administration. diff --git a/templates/i18n/faq/no-campus-account.de-de-formal.hamlet b/templates/i18n/faq/no-campus-account.de-de-formal.hamlet new file mode 100644 index 000000000..74710d329 --- /dev/null +++ b/templates/i18n/faq/no-campus-account.de-de-formal.hamlet @@ -0,0 +1,35 @@ +$newline never +

+ Uni2work-Administratoren können Uni2work-interne Kennungen ausstellen. + +

+ Wenden Sie sich dafür über das Hilfe-Formular, # + oben rechts auf jeder Seite, an die Uni2work-Administration. + +

+ Beschreiben Sie dabei bitte Ihre Situation und begründen Sie, warum # + Sie Zugriff auf das System benötigen. + +

+ Um einen Uni2work-internen Account zu erstellen werden folgende # + Informationen benötigt, senden Sie diese bitte direkt mit Ihrer # + Anfrage: + +

+
Akademischer Titel +
Vorname(n) +
Vollständiger Nachname +
Der Nachname kann aus mehreren Wörtern bestehen +
Adelstitel und Prädikate wie „von“ und „zu“ sind Bestandteil des Nachnames +
Vollständiger Name +
Der vollständige Name muss mindestens den kompletten Nachnamen enthalten +
Der vollständige Name kann zudem beliebige Teile der Vornamen und des akademischen Titels enthalten +
E-Mail Addresse zur Anzeige +
Wird anderen Benutzern angezeigt, wenn man z.B. als Kursverwalter oder Korrektor eingetragen ist +
Matrikelnummer +
Geschlecht +
„Unbekannt“, „Männlich“, „Weiblich“ oder „Keine Angabe“ +
Nach ISO 5218 +
E-Mail Addresse zum Versand +
An diese Addresse werden Mitteilungen von Uni2work versandt +
Die zuverlässige Zustellung muss gewährleistet sein, daher keine Emails von freien Mailanbietern wie GMail, Hotmail, GMX, etc.