feat(faqs): initial
This commit is contained in:
parent
4e8aaba782
commit
7b5337723d
@ -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
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
3
messages/faq/de-de-formal.msg
Normal file
3
messages/faq/de-de-formal.msg
Normal file
@ -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
|
||||
@ -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.
|
||||
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
|
||||
1
routes
1
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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
10
templates/faq.hamlet
Normal file
10
templates/faq.hamlet
Normal file
@ -0,0 +1,10 @@
|
||||
$newline never
|
||||
$forall (rItem, wgt) <- rItems
|
||||
<section .faq__section>
|
||||
<h2 .faq__question uw-show-hide data-show-hide-collapsed .show-hide__toggle ##{toPathPiece rItem}>
|
||||
_{rItem}
|
||||
\ #
|
||||
<a href=^{faqLink rItem}>
|
||||
<i .faq__question-link .fa .fa-link>
|
||||
<div .faq__answer>
|
||||
^{wgt}
|
||||
@ -1,4 +1,11 @@
|
||||
<section>
|
||||
_{MsgHelpIntroduction}
|
||||
$maybe (faqs, truncated) <- mFaqs
|
||||
^{faqs}
|
||||
$if truncated
|
||||
<section .faq__section>
|
||||
<h2 .faq__question>
|
||||
<a href=@{FaqR}>
|
||||
_{MsgAdditionalFaqs}
|
||||
<section>
|
||||
^{formWidget}
|
||||
|
||||
30
templates/i18n/faq/campus-cant-login.de-de-formal.hamlet
Normal file
30
templates/i18n/faq/campus-cant-login.de-de-formal.hamlet
Normal file
@ -0,0 +1,30 @@
|
||||
$newline never
|
||||
|
||||
<p>
|
||||
Können sie sich mit <i>exakt identischen</i> (idealerweise #
|
||||
copy&paste) Daten #
|
||||
im <a href="https://www.portal.uni-muenchen.de">Campus-Portal</a> #
|
||||
anmelden?<br />
|
||||
|
||||
Falls nicht ist davon auszugehen, dass Sie Ihre Anmeldedaten falsch #
|
||||
eingeben oder <a href=^{faqLink FAQNoCampusAccount}>keinen #
|
||||
Campus-Login besitzen</a>.
|
||||
|
||||
<p>
|
||||
Beachten Sie dabei auch, dass Uni2work Leerzeichen sowohl im #
|
||||
Passwort als auch bei der Kennung berücksichtigt.<br />
|
||||
|
||||
Beim Passwort ist zudem Groß- und Kleinschreibung relevant.
|
||||
|
||||
<p>
|
||||
Uni2work bietet zwei Login-Formulare.<br />
|
||||
|
||||
Für die Anmeldung mit der LMU Campus-Kennung müssen Sie das Formular #
|
||||
„Campus-Login“ verwenden.
|
||||
|
||||
<p>
|
||||
Falls Sie sich #
|
||||
im <a href="https://www.portal.uni-muenchen.de">Campus-Portal</a> #
|
||||
anmelden können, aber nicht in Uni2work, wenden Sie sich bitte über #
|
||||
das <a href=@{HelpR}>Hilfe-Formular</a>, oben rechts auf jeder #
|
||||
Seite, an die Uni2work-Administration.
|
||||
12
templates/i18n/faq/forgotten-password.de-de-formal.hamlet
Normal file
12
templates/i18n/faq/forgotten-password.de-de-formal.hamlet
Normal file
@ -0,0 +1,12 @@
|
||||
$newline never
|
||||
|
||||
<p>
|
||||
Wenn Sie sich gewöhnlicherweise mit Ihrem LMU Campus-Login anmelden, #
|
||||
wenden Sie sich bitte an #
|
||||
den <a href="https://www.it-servicedesk.uni-muenchen.de">IT-Servicedesk</a> #
|
||||
um Ihr Passwort zurücksetzen zu lassen.
|
||||
|
||||
<p>
|
||||
Wenn Sie sich mit einer Uni2work-internen Kennung anmelden wenden #
|
||||
Sie sich dafür bitte über das <a href=@{HelpR}>Hilfe-Formular</a>, #
|
||||
oben rechts auf jeder Seite, an die Uni2work-Administration.
|
||||
35
templates/i18n/faq/no-campus-account.de-de-formal.hamlet
Normal file
35
templates/i18n/faq/no-campus-account.de-de-formal.hamlet
Normal file
@ -0,0 +1,35 @@
|
||||
$newline never
|
||||
<p>
|
||||
Uni2work-Administratoren können Uni2work-interne Kennungen ausstellen.
|
||||
|
||||
<p>
|
||||
Wenden Sie sich dafür über das <a href=@{HelpR}>Hilfe-Formular</a>, #
|
||||
oben rechts auf jeder Seite, an die Uni2work-Administration.
|
||||
|
||||
<p>
|
||||
Beschreiben Sie dabei bitte Ihre Situation und begründen Sie, warum #
|
||||
Sie Zugriff auf das System benötigen.
|
||||
|
||||
<p>
|
||||
Um einen Uni2work-internen Account zu erstellen werden folgende #
|
||||
Informationen benötigt, senden Sie diese bitte direkt mit Ihrer #
|
||||
Anfrage:
|
||||
|
||||
<dl .deflist--no-grid>
|
||||
<dt>Akademischer Titel
|
||||
<dt>Vorname(n)
|
||||
<dt>Vollständiger Nachname
|
||||
<dd>Der Nachname kann aus mehreren Wörtern bestehen
|
||||
<dd>Adelstitel und Prädikate wie „von“ und „zu“ sind Bestandteil des Nachnames
|
||||
<dt>Vollständiger Name
|
||||
<dd>Der vollständige Name muss mindestens den kompletten Nachnamen enthalten
|
||||
<dd>Der vollständige Name kann zudem beliebige Teile der Vornamen und des akademischen Titels enthalten
|
||||
<dt>E-Mail Addresse zur Anzeige
|
||||
<dd>Wird anderen Benutzern angezeigt, wenn man z.B. als Kursverwalter oder Korrektor eingetragen ist
|
||||
<dt>Matrikelnummer
|
||||
<dt>Geschlecht
|
||||
<dd>„Unbekannt“, „Männlich“, „Weiblich“ oder „Keine Angabe“
|
||||
<dd>Nach <a href="https://en.wikipedia.org/wiki/ISO/IEC_5218">ISO 5218</a>
|
||||
<dt>E-Mail Addresse zum Versand
|
||||
<dd>An diese Addresse werden Mitteilungen von Uni2work versandt
|
||||
<dd>Die zuverlässige Zustellung muss gewährleistet sein, daher keine Emails von freien Mailanbietern wie GMail, Hotmail, GMX, etc.
|
||||
Loading…
Reference in New Issue
Block a user