feat(communication): send test emails
This commit is contained in:
parent
e060080261
commit
d90da85df3
@ -23,6 +23,7 @@
|
|||||||
"no-extra-semi": "off",
|
"no-extra-semi": "off",
|
||||||
"semi": ["error", "always"],
|
"semi": ["error", "always"],
|
||||||
"comma-dangle": ["error", "always-multiline"],
|
"comma-dangle": ["error", "always-multiline"],
|
||||||
"quotes": ["error", "single"]
|
"quotes": ["error", "single"],
|
||||||
|
"no-var": "error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
88
frontend/src/utils/form/communication-recipients.js
Normal file
88
frontend/src/utils/form/communication-recipients.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { Utility } from '../../core/utility';
|
||||||
|
|
||||||
|
const MASS_INPUT_SELECTOR = '.massinput';
|
||||||
|
const RECIPIENT_CATEGORIES_SELECTOR = '.recipient-categories';
|
||||||
|
const RECIPIENT_CATEGORY_SELECTOR = '.recipient-category';
|
||||||
|
const RECIPIENT_CATEGORY_CHECKBOX_SELECTOR = '.recipient-category__checkbox';
|
||||||
|
const RECIPIENT_CATEGORY_OPTIONS_SELECTOR = '.recipient-category__options';
|
||||||
|
const RECIPIENT_CATEGORY_TOGGLE_ALL_SELECTOR = '.recipient-category__toggle-all [type="checkbox"]';
|
||||||
|
const RECIPIENT_CATEGORY_CHECKED_COUNTER_SELECTOR = '.recipient-category__checked-counter';
|
||||||
|
|
||||||
|
|
||||||
|
@Utility({
|
||||||
|
selector: RECIPIENT_CATEGORIES_SELECTOR,
|
||||||
|
})
|
||||||
|
export class CommunicationRecipients {
|
||||||
|
massInputElement;
|
||||||
|
|
||||||
|
constructor(element) {
|
||||||
|
if (!element) {
|
||||||
|
throw new Error('Communication Recipient utility cannot be setup without an element!');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.massInputElement = element.closest(MASS_INPUT_SELECTOR);
|
||||||
|
|
||||||
|
this.setupRecipientCategories();
|
||||||
|
|
||||||
|
const recipientObserver = new MutationObserver(this.setupRecipientCategories.bind(this));
|
||||||
|
recipientObserver.observe(this.massInputElement, { childList: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
setupRecipientCategories() {
|
||||||
|
Array.from(this.massInputElement.querySelectorAll(RECIPIENT_CATEGORY_SELECTOR)).forEach(setupRecipientCategory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupRecipientCategory(recipientCategoryElement) {
|
||||||
|
const categoryCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_CHECKBOX_SELECTOR);
|
||||||
|
const categoryOptions = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_OPTIONS_SELECTOR);
|
||||||
|
|
||||||
|
if (categoryOptions) {
|
||||||
|
const categoryCheckboxes = Array.from(categoryOptions.querySelectorAll('[type="checkbox"]'));
|
||||||
|
const toggleAllCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_TOGGLE_ALL_SELECTOR);
|
||||||
|
|
||||||
|
// setup category checkbox to toggle all child checkboxes if changed
|
||||||
|
categoryCheckbox.addEventListener('change', () => {
|
||||||
|
categoryCheckboxes.filter(checkbox => !checkbox.disabled).forEach(checkbox => {
|
||||||
|
checkbox.checked = categoryCheckbox.checked;
|
||||||
|
});
|
||||||
|
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
||||||
|
updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes);
|
||||||
|
});
|
||||||
|
|
||||||
|
// update counter and toggle checkbox initially
|
||||||
|
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
||||||
|
updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes);
|
||||||
|
|
||||||
|
// register change listener for individual checkboxes
|
||||||
|
categoryCheckboxes.forEach(checkbox => {
|
||||||
|
checkbox.addEventListener('change', () => {
|
||||||
|
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
||||||
|
updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// register change listener for toggle all checkbox
|
||||||
|
if (toggleAllCheckbox) {
|
||||||
|
toggleAllCheckbox.addEventListener('change', () => {
|
||||||
|
categoryCheckboxes.filter(checkbox => !checkbox.disabled).forEach(checkbox => {
|
||||||
|
checkbox.checked = toggleAllCheckbox.checked;
|
||||||
|
});
|
||||||
|
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// update checked state of toggle all checkbox based on all other checkboxes
|
||||||
|
function updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes) {
|
||||||
|
const allChecked = categoryCheckboxes.reduce((acc, checkbox) => acc && checkbox.checked, true);
|
||||||
|
toggleAllCheckbox.checked = allChecked;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update value of checked counter
|
||||||
|
function updateCheckedCounter(recipientCategoryElement, categoryCheckboxes) {
|
||||||
|
const checkedCounter = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_CHECKED_COUNTER_SELECTOR);
|
||||||
|
const checkedCheckboxes = categoryCheckboxes.reduce((acc, checkbox) => checkbox.checked ? acc + 1 : acc, 0);
|
||||||
|
checkedCounter.innerHTML = checkedCheckboxes + '/' + categoryCheckboxes.length;
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import { Datepicker } from './datepicker';
|
|||||||
import { FormErrorRemover } from './form-error-remover';
|
import { FormErrorRemover } from './form-error-remover';
|
||||||
import { InteractiveFieldset } from './interactive-fieldset';
|
import { InteractiveFieldset } from './interactive-fieldset';
|
||||||
import { NavigateAwayPrompt } from './navigate-away-prompt';
|
import { NavigateAwayPrompt } from './navigate-away-prompt';
|
||||||
|
import { CommunicationRecipients } from './communication-recipients';
|
||||||
|
|
||||||
export const FormUtils = [
|
export const FormUtils = [
|
||||||
AutoSubmitButton,
|
AutoSubmitButton,
|
||||||
@ -13,5 +14,6 @@ export const FormUtils = [
|
|||||||
FormErrorRemover,
|
FormErrorRemover,
|
||||||
InteractiveFieldset,
|
InteractiveFieldset,
|
||||||
NavigateAwayPrompt,
|
NavigateAwayPrompt,
|
||||||
|
CommunicationRecipients,
|
||||||
// ReactiveSubmitButton // not used currently
|
// ReactiveSubmitButton // not used currently
|
||||||
];
|
];
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
@use "../../app" as *
|
||||||
|
|
||||||
// GENERAL STYLES FOR FORMS
|
// GENERAL STYLES FOR FORMS
|
||||||
|
|
||||||
// FORM GROUPS
|
// FORM GROUPS
|
||||||
@ -24,7 +26,7 @@
|
|||||||
margin-top: 40px
|
margin-top: 40px
|
||||||
|
|
||||||
.form-section-legend
|
.form-section-legend
|
||||||
color: var(--color-fontsec)
|
@extend .explanation
|
||||||
margin: 7px 0
|
margin: 7px 0
|
||||||
|
|
||||||
.form-group__hint, .form-section-title__hint
|
.form-group__hint, .form-section-title__hint
|
||||||
@ -51,6 +53,7 @@
|
|||||||
content: ' *'
|
content: ' *'
|
||||||
color: var(--color-error)
|
color: var(--color-error)
|
||||||
font-weight: 600
|
font-weight: 600
|
||||||
|
font-style: normal
|
||||||
|
|
||||||
.form-group--submit .form-group__input
|
.form-group--submit .form-group__input
|
||||||
grid-column: 2
|
grid-column: 2
|
||||||
|
|||||||
@ -37,6 +37,9 @@ BtnAllocationAccept: Vergabe akzeptieren
|
|||||||
BtnSystemMessageHide: Verstecken
|
BtnSystemMessageHide: Verstecken
|
||||||
BtnSystemMessageUnhide: Nicht mehr verstecken
|
BtnSystemMessageUnhide: Nicht mehr verstecken
|
||||||
|
|
||||||
|
BtnCommunicationSend: Senden
|
||||||
|
BtnCommunicationTest: Test-Nachricht verschicken
|
||||||
|
|
||||||
|
|
||||||
Aborted: Abgebrochen
|
Aborted: Abgebrochen
|
||||||
Remarks: Hinweise
|
Remarks: Hinweise
|
||||||
@ -952,6 +955,7 @@ MailEditNotifications: Benachrichtigungen ein-/ausschalten
|
|||||||
MailSubjectSupport: Supportanfrage
|
MailSubjectSupport: Supportanfrage
|
||||||
MailSubjectSupportCustom customSubject@Text: [Support] #{customSubject}
|
MailSubjectSupportCustom customSubject@Text: [Support] #{customSubject}
|
||||||
|
|
||||||
|
CommCourseTestSubject customSubject@Text: [TEST] #{customSubject}
|
||||||
CommCourseSubject: Kursmitteilung
|
CommCourseSubject: Kursmitteilung
|
||||||
MailSubjectLecturerInvitation tid@TermId ssh@SchoolId csh@CourseShorthand: [#{tid}-#{ssh}-#{csh}] Einladung zum Kursverwalter
|
MailSubjectLecturerInvitation tid@TermId ssh@SchoolId csh@CourseShorthand: [#{tid}-#{ssh}-#{csh}] Einladung zum Kursverwalter
|
||||||
InvitationAcceptDecline: Einladung annehmen/ablehnen
|
InvitationAcceptDecline: Einladung annehmen/ablehnen
|
||||||
@ -1415,6 +1419,7 @@ CommRecipientsTip: Sie selbst erhalten immer eine Kopie der Nachricht
|
|||||||
CommRecipientsList: Die an Sie selbst verschickte Kopie der Nachricht wird, zu Archivierungszwecken, eine vollständige Liste aller Empfänger enthalten. Die Empfängerliste wird im CSV-Format an die E-Mail angehängt. Andere Empfänger erhalten die Liste nicht. Bitte entfernen Sie dementsprechend den Anhang bevor Sie die E-Mail weiterleiten oder anderweitig mit Dritten teilen.
|
CommRecipientsList: Die an Sie selbst verschickte Kopie der Nachricht wird, zu Archivierungszwecken, eine vollständige Liste aller Empfänger enthalten. Die Empfängerliste wird im CSV-Format an die E-Mail angehängt. Andere Empfänger erhalten die Liste nicht. Bitte entfernen Sie dementsprechend den Anhang bevor Sie die E-Mail weiterleiten oder anderweitig mit Dritten teilen.
|
||||||
CommDuplicateRecipients n@Int: #{n} #{pluralDE n "doppelter" "doppelte"} Empfänger ignoriert
|
CommDuplicateRecipients n@Int: #{n} #{pluralDE n "doppelter" "doppelte"} Empfänger ignoriert
|
||||||
CommSuccess n@Int: Nachricht wurde an #{n} Empfänger versandt
|
CommSuccess n@Int: Nachricht wurde an #{n} Empfänger versandt
|
||||||
|
CommTestSuccess: Nachricht wurde zu Testzwecken nur an Sie selbst versandt
|
||||||
CommUndisclosedRecipients: Verborgene Empfänger
|
CommUndisclosedRecipients: Verborgene Empfänger
|
||||||
CommAllRecipients: alle-empfaenger
|
CommAllRecipients: alle-empfaenger
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,6 @@ import Import
|
|||||||
import Handler.Utils
|
import Handler.Utils
|
||||||
import Handler.Utils.Communication
|
import Handler.Utils.Communication
|
||||||
|
|
||||||
import qualified Data.CaseInsensitive as CI
|
|
||||||
|
|
||||||
import qualified Data.Set as Set
|
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
|
|
||||||
import qualified Database.Esqueleto as E
|
import qualified Database.Esqueleto as E
|
||||||
@ -18,23 +15,13 @@ import qualified Database.Esqueleto as E
|
|||||||
getCCommR, postCCommR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
getCCommR, postCCommR :: TermId -> SchoolId -> CourseShorthand -> Handler Html
|
||||||
getCCommR = postCCommR
|
getCCommR = postCCommR
|
||||||
postCCommR tid ssh csh = do
|
postCCommR tid ssh csh = do
|
||||||
jSender <- requireAuthId
|
|
||||||
cid <- runDB . getKeyBy404 $ TermSchoolCourseShort tid ssh csh
|
cid <- runDB . getKeyBy404 $ TermSchoolCourseShort tid ssh csh
|
||||||
|
|
||||||
commR CommunicationRoute
|
commR CommunicationRoute
|
||||||
{ crHeading = SomeMessage . prependCourseTitle tid ssh csh $ SomeMessage MsgCommCourseHeading
|
{ crHeading = SomeMessage . prependCourseTitle tid ssh csh $ SomeMessage MsgCommCourseHeading
|
||||||
, crUltDest = SomeRoute $ CourseR tid ssh csh CCommR
|
, crUltDest = SomeRoute $ CourseR tid ssh csh CCommR
|
||||||
, crJobs = \Communication{..} -> do
|
, crJobs = crJobsCourseCommunication cid
|
||||||
let jSubject = cSubject
|
, crTestJobs = crTestJobsCourseCommunication cid
|
||||||
jMailContent = cBody
|
|
||||||
jCourse = cid
|
|
||||||
allRecipients = Set.toList $ Set.insert (Right jSender) cRecipients
|
|
||||||
jMailObjectUUID <- liftIO getRandom
|
|
||||||
jAllRecipientAddresses <- lift . fmap Set.fromList . forM allRecipients $ \case
|
|
||||||
Left email -> return . Address Nothing $ CI.original email
|
|
||||||
Right rid -> userAddress <$> getJust rid
|
|
||||||
forM_ allRecipients $ \jRecipientEmail ->
|
|
||||||
yield JobSendCourseCommunication{..}
|
|
||||||
, crRecipients = Map.fromList
|
, crRecipients = Map.fromList
|
||||||
[ ( RGCourseParticipants
|
[ ( RGCourseParticipants
|
||||||
, E.from $ \(user `E.InnerJoin` participant) -> do
|
, E.from $ \(user `E.InnerJoin` participant) -> do
|
||||||
|
|||||||
@ -11,31 +11,18 @@ import qualified Database.Esqueleto as E
|
|||||||
import qualified Database.Esqueleto.Utils as E
|
import qualified Database.Esqueleto.Utils as E
|
||||||
|
|
||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import qualified Data.Set as Set
|
|
||||||
|
|
||||||
import qualified Data.CaseInsensitive as CI
|
|
||||||
|
|
||||||
|
|
||||||
getTCommR, postTCommR :: TermId -> SchoolId -> CourseShorthand -> TutorialName -> Handler Html
|
getTCommR, postTCommR :: TermId -> SchoolId -> CourseShorthand -> TutorialName -> Handler Html
|
||||||
getTCommR = postTCommR
|
getTCommR = postTCommR
|
||||||
postTCommR tid ssh csh tutn = do
|
postTCommR tid ssh csh tutn = do
|
||||||
jSender <- requireAuthId
|
|
||||||
(cid, tutid) <- runDB $ fetchCourseIdTutorialId tid ssh csh tutn
|
(cid, tutid) <- runDB $ fetchCourseIdTutorialId tid ssh csh tutn
|
||||||
|
|
||||||
commR CommunicationRoute
|
commR CommunicationRoute
|
||||||
{ crHeading = SomeMessage . prependCourseTitle tid ssh csh $ SomeMessage MsgCommTutorialHeading
|
{ crHeading = SomeMessage . prependCourseTitle tid ssh csh $ SomeMessage MsgCommTutorialHeading
|
||||||
, crUltDest = SomeRoute $ CTutorialR tid ssh csh tutn TCommR
|
, crUltDest = SomeRoute $ CTutorialR tid ssh csh tutn TCommR
|
||||||
, crJobs = \Communication{..} -> do
|
, crJobs = crJobsCourseCommunication cid
|
||||||
let jSubject = cSubject
|
, crTestJobs = crTestJobsCourseCommunication cid
|
||||||
jMailContent = cBody
|
|
||||||
jCourse = cid
|
|
||||||
allRecipients = Set.toList $ Set.insert (Right jSender) cRecipients
|
|
||||||
jMailObjectUUID <- liftIO getRandom
|
|
||||||
jAllRecipientAddresses <- lift . fmap Set.fromList . forM allRecipients $ \case
|
|
||||||
Left email -> return . Address Nothing $ CI.original email
|
|
||||||
Right rid -> userAddress <$> getJust rid
|
|
||||||
forM_ allRecipients $ \jRecipientEmail ->
|
|
||||||
yield JobSendCourseCommunication{..}
|
|
||||||
, crRecipients = Map.fromList
|
, crRecipients = Map.fromList
|
||||||
[ ( RGTutorialParticipants
|
[ ( RGTutorialParticipants
|
||||||
, E.from $ \(user `E.InnerJoin` participant) -> do
|
, E.from $ \(user `E.InnerJoin` participant) -> do
|
||||||
|
|||||||
@ -3,6 +3,7 @@ module Handler.Utils.Communication
|
|||||||
, CommunicationRoute(..)
|
, CommunicationRoute(..)
|
||||||
, Communication(..)
|
, Communication(..)
|
||||||
, commR
|
, commR
|
||||||
|
, crJobsCourseCommunication, crTestJobsCourseCommunication
|
||||||
-- * Re-Exports
|
-- * Re-Exports
|
||||||
, Job(..)
|
, Job(..)
|
||||||
) where
|
) where
|
||||||
@ -18,6 +19,8 @@ import Data.Map ((!), (!?))
|
|||||||
import qualified Data.Map as Map
|
import qualified Data.Map as Map
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
|
|
||||||
|
import qualified Data.Conduit.Combinators as C
|
||||||
|
|
||||||
|
|
||||||
data RecipientGroup = RGCourseParticipants | RGCourseLecturers | RGCourseCorrectors | RGCourseTutors
|
data RecipientGroup = RGCourseParticipants | RGCourseLecturers | RGCourseCorrectors | RGCourseTutors
|
||||||
| RGTutorialParticipants
|
| RGTutorialParticipants
|
||||||
@ -67,11 +70,25 @@ instance RenderMessage UniWorX RecipientCategory where
|
|||||||
renderMessage' :: forall msg. RenderMessage UniWorX msg => msg -> Text
|
renderMessage' :: forall msg. RenderMessage UniWorX msg => msg -> Text
|
||||||
renderMessage' = renderMessage foundation ls
|
renderMessage' = renderMessage foundation ls
|
||||||
|
|
||||||
|
data CommunicationButton
|
||||||
|
= BtnCommunicationSend
|
||||||
|
| BtnCommunicationTest
|
||||||
|
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
|
||||||
|
deriving anyclass (Universe, Finite)
|
||||||
|
|
||||||
|
nullaryPathPiece ''CommunicationButton $ camelToPathPiece' 2
|
||||||
|
embedRenderMessage ''UniWorX ''CommunicationButton id
|
||||||
|
makePrisms ''CommunicationButton
|
||||||
|
|
||||||
|
instance Button UniWorX CommunicationButton where
|
||||||
|
btnClasses BtnCommunicationSend = [BCIsButton, BCPrimary]
|
||||||
|
btnClasses BtnCommunicationTest = [BCIsButton]
|
||||||
|
|
||||||
|
|
||||||
data CommunicationRoute = CommunicationRoute
|
data CommunicationRoute = CommunicationRoute
|
||||||
{ crRecipients :: Map RecipientGroup (E.SqlQuery (E.SqlExpr (Entity User)))
|
{ crRecipients :: Map RecipientGroup (E.SqlQuery (E.SqlExpr (Entity User)))
|
||||||
, crRecipientAuth :: Maybe (UserId -> DB AuthResult) -- ^ Only resolve userids given as GET-Parameter if they fulfil this criterion
|
, crRecipientAuth :: Maybe (UserId -> DB AuthResult) -- ^ Only resolve userids given as GET-Parameter if they fulfil this criterion
|
||||||
, crJobs :: Communication -> ConduitT () Job (YesodDB UniWorX) ()
|
, crJobs, crTestJobs :: Communication -> ConduitT () Job (YesodDB UniWorX) ()
|
||||||
, crHeading :: SomeMessage UniWorX
|
, crHeading :: SomeMessage UniWorX
|
||||||
, crUltDest :: SomeRoute UniWorX
|
, crUltDest :: SomeRoute UniWorX
|
||||||
}
|
}
|
||||||
@ -83,6 +100,26 @@ data Communication = Communication
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
crJobsCourseCommunication, crTestJobsCourseCommunication :: CourseId -> Communication -> ConduitT () Job (YesodDB UniWorX) ()
|
||||||
|
crJobsCourseCommunication jCourse Communication{..} = do
|
||||||
|
jSender <- requireAuthId
|
||||||
|
let jSubject = cSubject
|
||||||
|
jMailContent = cBody
|
||||||
|
allRecipients = Set.toList $ Set.insert (Right jSender) cRecipients
|
||||||
|
jMailObjectUUID <- liftIO getRandom
|
||||||
|
jAllRecipientAddresses <- lift . fmap Set.fromList . forM allRecipients $ \case
|
||||||
|
Left email -> return . Address Nothing $ CI.original email
|
||||||
|
Right rid -> userAddress <$> getJust rid
|
||||||
|
forM_ allRecipients $ \jRecipientEmail ->
|
||||||
|
yield JobSendCourseCommunication{..}
|
||||||
|
crTestJobsCourseCommunication jCourse comm = do
|
||||||
|
jSender <- requireAuthId
|
||||||
|
|
||||||
|
MsgRenderer mr <- getMsgRenderer
|
||||||
|
let comm' = comm { cSubject = Just . mr . MsgCommCourseTestSubject . fromMaybe (mr MsgCommCourseSubject) $ cSubject comm }
|
||||||
|
crJobsCourseCommunication jCourse comm' .| C.filter ((== Right jSender) . jRecipientEmail)
|
||||||
|
|
||||||
|
|
||||||
commR :: CommunicationRoute -> Handler Html
|
commR :: CommunicationRoute -> Handler Html
|
||||||
commR CommunicationRoute{..} = do
|
commR CommunicationRoute{..} = do
|
||||||
cUser <- maybeAuth
|
cUser <- maybeAuth
|
||||||
@ -129,14 +166,18 @@ commR CommunicationRoute{..} = do
|
|||||||
miAdd (EnumPosition RecipientCustom, 0) 1 nudge submitView = Just $ \csrf -> do
|
miAdd (EnumPosition RecipientCustom, 0) 1 nudge submitView = Just $ \csrf -> do
|
||||||
(addRes, addView) <- mpreq (multiUserField True Nothing) (fslpI MsgEMail (mr MsgEMail) & setTooltip MsgMultiEmailFieldTip & addName (nudge "email")) Nothing
|
(addRes, addView) <- mpreq (multiUserField True Nothing) (fslpI MsgEMail (mr MsgEMail) & setTooltip MsgMultiEmailFieldTip & addName (nudge "email")) Nothing
|
||||||
let
|
let
|
||||||
addRes' = addRes <&> \(Set.toList -> nEmails) (maybe 0 (succ . snd . fst) . Map.lookupMax . Map.filterWithKey (\(EnumPosition c, _) _ -> c == RecipientCustom) -> kStart) -> FormSuccess . Map.fromList $ zip (map (EnumPosition RecipientCustom, ) [kStart..]) nEmails
|
addRes' = addRes <&> \nEmails ((Map.elems &&& maybe 0 (succ . snd . fst) . Map.lookupMax) . Map.filterWithKey (\(EnumPosition c, _) _ -> c == RecipientCustom) -> (oEmails, kStart)) -> FormSuccess . Map.fromList . zip (map (EnumPosition RecipientCustom, ) [kStart..]) . Set.toList $ nEmails `Set.difference` Set.fromList oEmails
|
||||||
return (addRes', $(widgetFile "widgets/communication/recipientAdd"))
|
return (addRes', $(widgetFile "widgets/communication/recipientAdd"))
|
||||||
miAdd _ _ _ _ = Nothing
|
miAdd _ _ _ _ = Nothing
|
||||||
miCell _ (Left (CI.original -> email)) initRes nudge csrf = do
|
miCell _ (Left (CI.original -> email)) initRes nudge csrf = do
|
||||||
(tickRes, tickView) <- mpreq checkBoxField ("" & addName (nudge "tick")) $ initRes <|> Just True
|
(tickRes, tickView) <- mpreq checkBoxField ("" & addName (nudge "tick")) $ initRes <|> Just True
|
||||||
return (tickRes, $(widgetFile "widgets/communication/recipientEmail"))
|
return (tickRes, $(widgetFile "widgets/communication/recipientEmail"))
|
||||||
miCell _ (Right (lookupUser -> User{..})) initRes nudge csrf = do
|
miCell _ (Right uid@(lookupUser -> User{..})) initRes nudge csrf = do
|
||||||
(tickRes, tickView) <- mpreq checkBoxField ("" & addName (nudge "tick")) $ initRes <|> Just True
|
(tickRes, tickView) <- if
|
||||||
|
| fmap entityKey cUser == Just uid
|
||||||
|
-> mforced checkBoxField ("" & addName (nudge "tick")) True
|
||||||
|
| otherwise
|
||||||
|
-> mpreq checkBoxField ("" & addName (nudge "tick")) $ initRes <|> Just True
|
||||||
return (tickRes, $(widgetFile "widgets/communication/recipientName"))
|
return (tickRes, $(widgetFile "widgets/communication/recipientName"))
|
||||||
miAllowAdd (EnumPosition RecipientCustom, 0) 1 _ = True
|
miAllowAdd (EnumPosition RecipientCustom, 0) 1 _ = True
|
||||||
miAllowAdd _ _ _ = False
|
miAllowAdd _ _ _ = False
|
||||||
@ -167,22 +208,33 @@ commR CommunicationRoute{..} = do
|
|||||||
|
|
||||||
recipientsListMsg <- messageI Info MsgCommRecipientsList
|
recipientsListMsg <- messageI Info MsgCommRecipientsList
|
||||||
|
|
||||||
((commRes,commWdgt),commEncoding) <- runFormPost . identifyForm FIDCommunication . renderAForm FormStandard $ Communication
|
((commRes,commWdgt),commEncoding) <- runFormPost . identifyForm FIDCommunication . withButtonForm' universeF . renderAForm FormStandard $ Communication
|
||||||
<$> recipientAForm
|
<$> recipientAForm
|
||||||
<* aformMessage recipientsListMsg
|
<* aformMessage recipientsListMsg
|
||||||
<*> aopt textField (fslI MsgCommSubject) Nothing
|
<*> aopt textField (fslI MsgCommSubject) Nothing
|
||||||
<*> areq htmlField (fslI MsgCommBody) Nothing
|
<*> areq htmlField (fslI MsgCommBody) Nothing
|
||||||
formResult commRes $ \comm -> do
|
formResult commRes $ \case
|
||||||
runDBJobs . runConduit $ transPipe (mapReaderT lift) (crJobs comm) .| sinkDBJobs
|
(comm, BtnCommunicationSend) -> do
|
||||||
addMessageI Success . MsgCommSuccess . Set.size $ cRecipients comm
|
runDBJobs . runConduit $ transPipe (mapReaderT lift) (crJobs comm) .| sinkDBJobs
|
||||||
redirect crUltDest
|
addMessageI Success . MsgCommSuccess . Set.size $ cRecipients comm
|
||||||
|
redirect crUltDest
|
||||||
|
(comm, BtnCommunicationTest) -> do
|
||||||
|
runDBJobs . runConduit $ transPipe (mapReaderT lift) (crTestJobs comm) .| sinkDBJobs
|
||||||
|
addMessageI Info MsgCommTestSuccess
|
||||||
|
|
||||||
let formWdgt = wrapForm commWdgt def
|
let formWdgt = wrapForm commWdgt def
|
||||||
{ formMethod = POST
|
{ formMethod = POST
|
||||||
, formAction = SomeRoute <$> mbCurrentRoute
|
, formAction = SomeRoute <$> mbCurrentRoute
|
||||||
, formEncoding = commEncoding
|
, formEncoding = commEncoding
|
||||||
|
, formSubmit = FormNoSubmit
|
||||||
}
|
}
|
||||||
siteLayoutMsg crHeading $ do
|
siteLayoutMsg crHeading $ do
|
||||||
setTitleI crHeading
|
setTitleI crHeading
|
||||||
formWdgt
|
let commTestTip = $(i18nWidgetFile "comm-test-tip")
|
||||||
|
[whamlet|
|
||||||
|
$newline never
|
||||||
|
<section>
|
||||||
|
^{formWdgt}
|
||||||
|
<section .explanation>
|
||||||
|
^{commTestTip}
|
||||||
|
|]
|
||||||
|
|||||||
17
templates/i18n/comm-test-tip/de-de-formal.hamlet
Normal file
17
templates/i18n/comm-test-tip/de-de-formal.hamlet
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
$newline never
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
Über den Knopf „_{MsgBtnCommunicationTest}“ haben Sie die #
|
||||||
|
Möglichkeit zunächst eine Kopie der Nachricht nur an sich selbst zu #
|
||||||
|
schicken.
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Diese wird den Präfix „[TEST]“ im Betreff enthalten und ansonsten in #
|
||||||
|
allen Aspekten identisch sein zur eigentlichen Nachricht.
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
Um die Nachricht tatsächlich an alle Empfänger zu verschicken, #
|
||||||
|
verwenden Sie bitte „_{MsgBtnSubmit}“.
|
||||||
@ -1,87 +0,0 @@
|
|||||||
(function() {
|
|
||||||
|
|
||||||
var MASS_INPUT_SELECTOR = '.massinput';
|
|
||||||
var RECIPIENT_CATEGORIES_SELECTOR = '.recipient-categories';
|
|
||||||
var RECIPIENT_CATEGORY_SELECTOR = '.recipient-category';
|
|
||||||
var RECIPIENT_CATEGORY_CHECKBOX_SELECTOR = '.recipient-category__checkbox ';
|
|
||||||
var RECIPIENT_CATEGORY_OPTIONS_SELECTOR = '.recipient-category__options';
|
|
||||||
var RECIPIENT_CATEGORY_TOGGLE_ALL_SELECTOR = '.recipient-category__toggle-all [type="checkbox"]';
|
|
||||||
var RECIPIENT_CATEGORY_CHECKED_COUNTER_SELECTOR = '.recipient-category__checked-counter';
|
|
||||||
|
|
||||||
var massInputElement;
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
var recipientCategoriesElement = document.querySelector(RECIPIENT_CATEGORIES_SELECTOR);
|
|
||||||
massInputElement = recipientCategoriesElement.closest(MASS_INPUT_SELECTOR);
|
|
||||||
|
|
||||||
setupRecipientCategories();
|
|
||||||
|
|
||||||
var recipientObserver = new MutationObserver(setupRecipientCategories);
|
|
||||||
recipientObserver.observe(massInputElement, { childList: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
function setupRecipientCategories() {
|
|
||||||
var recipientCategoryElements = Array.from(massInputElement.querySelectorAll(RECIPIENT_CATEGORY_SELECTOR));
|
|
||||||
|
|
||||||
recipientCategoryElements.forEach(function(element) {
|
|
||||||
setupRecipientCategory(element);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupRecipientCategory(recipientCategoryElement) {
|
|
||||||
var categoryCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_CHECKBOX_SELECTOR);
|
|
||||||
var categoryOptions = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_OPTIONS_SELECTOR);
|
|
||||||
if (categoryOptions) {
|
|
||||||
|
|
||||||
var categoryCheckboxes = Array.from(categoryOptions.querySelectorAll('[type="checkbox"]'));
|
|
||||||
var toggleAllCheckbox = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_TOGGLE_ALL_SELECTOR);
|
|
||||||
|
|
||||||
// setup category checkbox to toggle all child checkboxes if changed
|
|
||||||
categoryCheckbox.addEventListener('change', function() {
|
|
||||||
categoryCheckboxes.forEach(function(checkbox) {
|
|
||||||
checkbox.checked = categoryCheckbox.checked;
|
|
||||||
});
|
|
||||||
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
|
||||||
updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes);
|
|
||||||
});
|
|
||||||
|
|
||||||
// update counter and toggle checkbox initially
|
|
||||||
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
|
||||||
updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes);
|
|
||||||
|
|
||||||
// register change listener for individual checkboxes
|
|
||||||
categoryCheckboxes.forEach(function(checkbox) {
|
|
||||||
checkbox.addEventListener('change', function() {
|
|
||||||
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
|
||||||
updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// register change listener for toggle all checkbox
|
|
||||||
if (toggleAllCheckbox) {
|
|
||||||
toggleAllCheckbox.addEventListener('change', function() {
|
|
||||||
categoryCheckboxes.forEach(function(checkbox) {
|
|
||||||
checkbox.checked = toggleAllCheckbox.checked;
|
|
||||||
});
|
|
||||||
updateCheckedCounter(recipientCategoryElement, categoryCheckboxes);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update checked state of toggle all checkbox based on all other checkboxes
|
|
||||||
function updateToggleAllCheckbox(toggleAllCheckbox, categoryCheckboxes) {
|
|
||||||
var allChecked = categoryCheckboxes.reduce(function(acc, checkbox) {
|
|
||||||
return acc && checkbox.checked;
|
|
||||||
}, true);
|
|
||||||
toggleAllCheckbox.checked = allChecked;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update value of checked counter
|
|
||||||
function updateCheckedCounter(recipientCategoryElement, categoryCheckboxes) {
|
|
||||||
var checkedCounter = recipientCategoryElement.querySelector(RECIPIENT_CATEGORY_CHECKED_COUNTER_SELECTOR);
|
|
||||||
var checkedCheckboxes = categoryCheckboxes.reduce(function(acc, checkbox) { return checkbox.checked ? acc + 1 : acc; }, 0);
|
|
||||||
checkedCounter.innerHTML = checkedCheckboxes + '/' + categoryCheckboxes.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
||||||
Loading…
Reference in New Issue
Block a user