This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository pollen. See https://gitlab.nuiton.org/chorem/pollen.git commit 701218781f95a038d9c5dbf1adb0cec8220665a9 Author: Sylvain Bavencoff <bavencoff@codelutin.com> Date: Mon Jun 11 17:25:25 2018 +0200 refs #192 : Restreindre un sondage sur les emails des votants --- .../chorem/pollen/persistence/entity/Polls.java | 4 + .../V3_2_0_3__add_parcicipants_Filters_in_poll.sql | 2 + .../V3_2_0_3__add_parcicipants_Filters_in_poll.sql | 2 + pollen-persistence/src/main/xmi/pollen.properties | 2 +- pollen-persistence/src/main/xmi/pollen.zargo | Bin 30924 -> 31056 bytes .../org/chorem/pollen/services/bean/PollBean.java | 11 ++ .../pollen/services/service/PollService.java | 18 +++ .../services/service/security/SecurityService.java | 26 +++- pollen-ui-riot-js/src/main/web/i18n/en.json | 18 ++- pollen-ui-riot-js/src/main/web/i18n/fr.json | 18 ++- pollen-ui-riot-js/src/main/web/js/PollForm.js | 13 +- .../src/main/web/tag/poll/Settings.tag.html | 152 ++++++++++++++++----- .../src/main/web/tag/poll/Summary.tag.html | 18 ++- .../pollen-votecounting-condorcet_en_GB.properties | 2 +- .../pollen-votecounting-condorcet_fr_FR.properties | 2 +- 15 files changed, 233 insertions(+), 55 deletions(-) diff --git a/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/Polls.java b/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/Polls.java index 76b9c00a..460057a8 100644 --- a/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/Polls.java +++ b/pollen-persistence/src/main/java/org/chorem/pollen/persistence/entity/Polls.java @@ -40,6 +40,10 @@ public class Polls { return Objects.equals(PollType.RESTRICTED, poll.getPollType()); } + public static boolean isPollRegistered(Poll poll) { + return Objects.equals(PollType.REGISTERED, poll.getPollType()); + } + public static boolean isStarted(Poll poll, Date currentDate) { Date beginDate = poll.getBeginDate(); return beginDate == null || beginDate.before(currentDate); diff --git a/pollen-persistence/src/main/resources/db/migration/h2/V3_2_0_3__add_parcicipants_Filters_in_poll.sql b/pollen-persistence/src/main/resources/db/migration/h2/V3_2_0_3__add_parcicipants_Filters_in_poll.sql new file mode 100644 index 00000000..d4d5830b --- /dev/null +++ b/pollen-persistence/src/main/resources/db/migration/h2/V3_2_0_3__add_parcicipants_Filters_in_poll.sql @@ -0,0 +1,2 @@ +-- add participant filter in Poll +alter table poll add emailAddressSuffixes LONGVARCHAR; diff --git a/pollen-persistence/src/main/resources/db/migration/postgresql/V3_2_0_3__add_parcicipants_Filters_in_poll.sql b/pollen-persistence/src/main/resources/db/migration/postgresql/V3_2_0_3__add_parcicipants_Filters_in_poll.sql new file mode 100644 index 00000000..83a4e9f8 --- /dev/null +++ b/pollen-persistence/src/main/resources/db/migration/postgresql/V3_2_0_3__add_parcicipants_Filters_in_poll.sql @@ -0,0 +1,2 @@ +-- add participant filter in Poll +alter table poll add emailAddressSuffixes text; diff --git a/pollen-persistence/src/main/xmi/pollen.properties b/pollen-persistence/src/main/xmi/pollen.properties index 8001c99f..ff910a8e 100644 --- a/pollen-persistence/src/main/xmi/pollen.properties +++ b/pollen-persistence/src/main/xmi/pollen.properties @@ -18,7 +18,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # #L% ###m -model.tagvalue.version=3.2.0.2 +model.tagvalue.version=3.2.0.3 #model.tagValue.notGenerateToString=true #model.tagValue.constantPrefix=PROPERTY_ #model.tagValue.useEnumerationName=true diff --git a/pollen-persistence/src/main/xmi/pollen.zargo b/pollen-persistence/src/main/xmi/pollen.zargo index 52b95159..419ff5bf 100644 Binary files a/pollen-persistence/src/main/xmi/pollen.zargo and b/pollen-persistence/src/main/xmi/pollen.zargo differ diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/bean/PollBean.java b/pollen-services/src/main/java/org/chorem/pollen/services/bean/PollBean.java index e117cac2..5cd77515 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/bean/PollBean.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/bean/PollBean.java @@ -31,6 +31,7 @@ import org.chorem.pollen.persistence.entity.VoteVisibility; import org.chorem.pollen.votecounting.model.VoteCountingConfig; import java.util.Date; +import java.util.List; import java.util.Objects; import java.util.Set; @@ -147,6 +148,8 @@ public class PollBean extends PollenBean<Poll> { protected VoteCountingConfig voteCountingConfig; + protected List<String> emailAddressSuffixes; + public String getPermission() { return permission; } @@ -493,4 +496,12 @@ public class PollBean extends PollenBean<Poll> { public void setInvalidEmails(Set<String> invalidEmails) { this.invalidEmails = invalidEmails; } + + public List<String> getEmailAddressSuffixes() { + return emailAddressSuffixes; + } + + public void setEmailAddressSuffixes(List<String> emailAddressSuffixes) { + this.emailAddressSuffixes = emailAddressSuffixes; + } } diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/PollService.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/PollService.java index 97dc8ea2..6f20b476 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/service/PollService.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/PollService.java @@ -22,8 +22,10 @@ package org.chorem.pollen.services.service; */ import com.google.common.base.Joiner; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; import org.chorem.pollen.persistence.entity.Choice; import org.chorem.pollen.persistence.entity.ChoiceType; @@ -57,6 +59,7 @@ import java.util.Date; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import static org.nuiton.i18n.I18n.l; @@ -68,6 +71,8 @@ import static org.nuiton.i18n.I18n.l; */ public class PollService extends PollenServiceSupport { + public static String EMAIL_SUFFIX_SEPARATOR = ";"; + public PollBean toPollBean(Poll entity) { PollBean bean = new PollBean(); @@ -116,6 +121,10 @@ public class PollService extends PollenServiceSupport { bean.setNewChoiceNotification(entity.isNewChoiceNotification()); bean.setNotificationLocale(entity.getNotificationLocale()); bean.setVoteCountingConfig(getVoteCountingService().getVoteCountingConfig(entity)); + String emailAddressSuffixes = entity.getEmailAddressSuffixes(); + if (StringUtils.isNotBlank(emailAddressSuffixes)) { + bean.setEmailAddressSuffixes(Lists.newArrayList(emailAddressSuffixes.split(PollService.EMAIL_SUFFIX_SEPARATOR))); + } Date now = getNow(); if (Polls.isFinished(entity, now)) { @@ -572,6 +581,15 @@ public class PollService extends PollenServiceSupport { toSave.setBeginDate(poll.getBeginDate()); toSave.setEndDate(poll.getEndDate()); + String suffixes = null; + if (CollectionUtils.isNotEmpty(poll.getEmailAddressSuffixes())) { + suffixes = poll.getEmailAddressSuffixes().stream() + .filter(StringUtils::isNotBlank) + .collect(Collectors.joining(PollService.EMAIL_SUFFIX_SEPARATOR)); + + } + toSave.setEmailAddressSuffixes(suffixes); + if (toSave.getBeginDate() == null) { toSave.setBeginDate(getNow()); } diff --git a/pollen-services/src/main/java/org/chorem/pollen/services/service/security/SecurityService.java b/pollen-services/src/main/java/org/chorem/pollen/services/service/security/SecurityService.java index 9748e54d..0a9903a9 100644 --- a/pollen-services/src/main/java/org/chorem/pollen/services/service/security/SecurityService.java +++ b/pollen-services/src/main/java/org/chorem/pollen/services/service/security/SecurityService.java @@ -39,6 +39,7 @@ import org.chorem.pollen.persistence.entity.PollenPrincipalTopiaDao; import org.chorem.pollen.persistence.entity.PollenToken; import org.chorem.pollen.persistence.entity.PollenTokenTopiaDao; import org.chorem.pollen.persistence.entity.PollenUser; +import org.chorem.pollen.persistence.entity.PollenUserEmailAddress; import org.chorem.pollen.persistence.entity.Polls; import org.chorem.pollen.persistence.entity.ResultVisibility; import org.chorem.pollen.persistence.entity.SessionToken; @@ -50,6 +51,7 @@ import org.chorem.pollen.services.PollenServiceContext; import org.chorem.pollen.services.PollenTechnicalException; import org.chorem.pollen.services.bean.PollenEntityRef; import org.chorem.pollen.services.bean.UsersRight; +import org.chorem.pollen.services.service.PollService; import org.chorem.pollen.services.service.PollenServiceSupport; import org.nuiton.topia.persistence.TopiaNoResultException; @@ -58,6 +60,7 @@ import java.util.Calendar; import java.util.Date; import java.util.Objects; import java.util.Set; +import java.util.stream.Stream; /** * TODO @@ -347,7 +350,8 @@ public class SecurityService extends PollenServiceSupport { return isAdmin() || Polls.isPollFree(poll) || matchPrincipal(poll.getCreator()) - || isInvited(poll); + || Polls.isPollRestricted(poll) && isInvited(poll) + || Polls.isPollRegistered(poll) && isRegisteredAndAccept(poll); } protected boolean isInvited(Poll poll) { @@ -363,6 +367,21 @@ public class SecurityService extends PollenServiceSupport { .exists(); } + protected boolean isRegisteredAndAccept(Poll poll) { + String suffixes = poll.getEmailAddressSuffixes(); + return getSecurityContext().isConnected() && (StringUtils.isBlank(suffixes) || + Stream.of(suffixes.split(PollService.EMAIL_SUFFIX_SEPARATOR)) + .anyMatch(this::isConnectedUserAcceptEmailSuffix) + ); + } + + protected boolean isConnectedUserAcceptEmailSuffix(String suffix) { + return getConnectedUser().getEmailAddresses().stream() + .filter(email -> email.getActivationToken() == null) // email Validate + .map(PollenUserEmailAddress::getEmailAddress) + .anyMatch(email -> email.endsWith(suffix)); + } + protected boolean hasVoted(Poll poll) { return (getSecurityContext().getMainPrincipal() != null && getVoteDao() @@ -423,7 +442,10 @@ public class SecurityService extends PollenServiceSupport { public boolean canAddVote(Poll poll) { return Polls.isRunning(poll, getNow()) - && (Polls.isPollFree(poll) || Polls.isPollRestricted(poll) && isInvited(poll) && !hasVoted(poll)); + && (Polls.isPollFree(poll) + || Polls.isPollRestricted(poll) && isInvited(poll) && !hasVoted(poll) + || Polls.isPollRegistered(poll) && isRegisteredAndAccept(poll) && !hasVoted(poll) + ); } public boolean canEdit(Poll poll) { diff --git a/pollen-ui-riot-js/src/main/web/i18n/en.json b/pollen-ui-riot-js/src/main/web/i18n/en.json index 4880471c..f940cf6a 100644 --- a/pollen-ui-riot-js/src/main/web/i18n/en.json +++ b/pollen-ui-riot-js/src/main/web/i18n/en.json @@ -37,6 +37,8 @@ "summary_membersNoInvited_one": "1 Invitation email has not been sent yet", "summary_sendInvitation": "Send invitation", "summary_sendInvitations": "Send {0} invitations", + "summary_registered_filter": "Only registered users whose email address verifies any of the following suffixes can vote", + "summary_registered_all": "Only registered users can vote", "summary_sendInvitations_success": "The {0} invitations are sent", "summary_sendInvitation_success": "The invitation is sent", "summary_closePoll": "Close poll", @@ -287,9 +289,19 @@ "poll_description_nameNotBlank": "Name nust be not blank", "poll_description_email": "Your email", "poll_description_emailPlaceHolder": "Enter your email", - "poll_settings_restricted": "Restricted poll ?", - "poll_settings_restricted_yes": "Only those invited to the next stage can vote. An email will be sent to them with a personal link.", - "poll_settings_restricted_no": "All people with the poll link can vote", + "poll_settings_pollType": "Poll opening", + "poll_settings_pollType_FREE": "Open to all", + "poll_settings_pollType_FREE_help": "Anyone with the poll link can vote.", + "poll_settings_pollType_RESTRICTED": "Restricted", + "poll_settings_pollType_RESTRICTED_help": "Only those invited to the next step can vote. An email will be sent to them with a personal link.", + "poll_settings_pollType_REGISTERED": "Registered uusers", + "poll_settings_pollType_REGISTERED_help": "Only registered users can vote.", + "poll_settings_pollType_REGISTERED_limitEmail": "Limit to certain email addresses", + "poll_settings_pollType_REGISTERED_suffix": "addresse suffix", + "poll_settings_pollType_REGISTERED_suffixPlaceHolder": "@pollen.cl", + "poll_settings_pollType_REGISTERED_suffix_remove": "Remove this suffix", + "poll_settings_pollType_REGISTERED_suffix_add": "Add", + "poll_settings_pollType_REGISTERED_suffix_add_title": "Add a email addresse suffix", "poll_settings_basic_usage": "For a basic poll, you can skip this step.", "poll_settings_use_basic_usage": "Use the default options", "poll_settings_basic_usage_detail": "Default Pollen offers you : ", diff --git a/pollen-ui-riot-js/src/main/web/i18n/fr.json b/pollen-ui-riot-js/src/main/web/i18n/fr.json index 3353992d..86449fd5 100644 --- a/pollen-ui-riot-js/src/main/web/i18n/fr.json +++ b/pollen-ui-riot-js/src/main/web/i18n/fr.json @@ -35,6 +35,8 @@ "summary_membersNoInvited_all": "Les mails d'invitations n'ont pas encore été envoyés", "summary_membersNoInvited_many": "{0} mails d'invitation n'ont pas encore été envoyés", "summary_membersNoInvited_one": "1 mails d'invitation n'a pas encore été envoyé", + "summary_registered_filter": "Seul les utilisateurs enregistrés et dont l'adresse électronique vérifie l'un des suffixes suivants peuvent voter", + "summary_registered_all": "Seul les utilisateurs enregistrés peuvent voter", "summary_sendInvitation": "Envoyer l'invitation", "summary_sendInvitations": "Envoyer les {0} invitations", "summary_sendInvitations_success": "Les {0} invitations sont envoyées", @@ -287,9 +289,19 @@ "poll_description_nameNotBlank": "Vote nom ne doit pas être blanc", "poll_description_email": "Votre adresse électronique", "poll_description_emailPlaceHolder": "Renseignez votre adresse électronique", - "poll_settings_restricted": "Sondage restreint ?", - "poll_settings_restricted_yes": "Seules les personnes invitées à l'etape suivante peuvent voter. Un courriel leur sera envoyé avec un lien personnel.", - "poll_settings_restricted_no": "Toutes les personnes ayant le lien du sondage peuvent voter.", + "poll_settings_pollType": "Ouverture du sondage", + "poll_settings_pollType_FREE": "Ouvert à tous", + "poll_settings_pollType_FREE_help": "Toutes les personnes ayant le lien du sondage peuvent voter.", + "poll_settings_pollType_RESTRICTED": "Restreint à une liste", + "poll_settings_pollType_RESTRICTED_help": "Seules les personnes invitées à l'etape suivante peuvent voter. Un courriel leur sera envoyé avec un lien personnel.", + "poll_settings_pollType_REGISTERED": "Utilisateurs enregistrés", + "poll_settings_pollType_REGISTERED_help": "Seules les utilisateurs enregistrés peuvent voter.", + "poll_settings_pollType_REGISTERED_limitEmail": "Limiter à certaines adresse électroniques", + "poll_settings_pollType_REGISTERED_suffix": "suffix de l'adresse", + "poll_settings_pollType_REGISTERED_suffixPlaceHolder": "@pollen.cl", + "poll_settings_pollType_REGISTERED_suffix_remove": "Supprimer le suffix", + "poll_settings_pollType_REGISTERED_suffix_add": "Ajouter", + "poll_settings_pollType_REGISTERED_suffix_add_title": "Ajouter un suffix d'adresse électronique", "poll_settings_basic_usage": "Options par défaut", "poll_settings_use_basic_usage": "Utiliser les options par défaut", "poll_settings_basic_usage_detail": "Par défaut Pollen vous propose : ", diff --git a/pollen-ui-riot-js/src/main/web/js/PollForm.js b/pollen-ui-riot-js/src/main/web/js/PollForm.js index 52c38fb4..273546d5 100644 --- a/pollen-ui-riot-js/src/main/web/js/PollForm.js +++ b/pollen-ui-riot-js/src/main/web/js/PollForm.js @@ -38,6 +38,7 @@ class PollForm { "options", "voters" ]; + this.types = ["FREE", "RESTRICTED", "REGISTERED"]; this.pollService = pollService; this.pageTracker = pageTracker; @@ -279,14 +280,12 @@ class PollForm { this.model.commentVisibility = "EVERYBODY"; } - togglePollType() { - if (this.model.pollType === "RESTRICTED") { - this.model.pollType = "FREE"; - } else { - this.model.pollType = "RESTRICTED"; + setPollType(type) { + if (this.types.indexOf(type) >= 0 && this.model.pollType !== type) { + this.model.pollType = type; + this._updateSteps(); + bus.trigger("pollStepsChange", this.steps); } - this._updateSteps(); - bus.trigger("pollStepsChange", this.steps); } _updateSteps() { diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/Settings.tag.html b/pollen-ui-riot-js/src/main/web/tag/poll/Settings.tag.html index 9f7c56c1..eace7e03 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/Settings.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/poll/Settings.tag.html @@ -28,23 +28,73 @@ import "../voteCountingType/CumulativeConfig.tag.html"; import "../voteCountingType/MajorityJudgmentConfig.tag.html"; <Settings> - <div class="form-section" show={form.creation}> + <div class="form-section"> + <h4><i class="fa fa-users"></i> {_t.pollType}</h4> <div class="o-form-element"> - <Checkbox label="{_t.restricted}" - name="pollType" - ref="pollType" - i18nprefix="{i18nprefix}" - disabled={opts.form.hasVotes} - checkboxchecked={opts.form.model.pollType === "RESTRICTED"} - ontogglecheckbox={togglePollType}> - {checked ? _t.restricted_yes : _t.restricted_no} - </Checkbox> + <span class="c-input-group"> + <button each={type in form.types} + class="c-button c-button--brand {c-button--ghost-brand: form.model.pollType !== type, c-button--brand: form.model.pollType === type}" + onclick={pollTypeChange(type)} + title={_l("pollType_" + type)} + ref={"pollType_" + type} + disabled={form.hasVotes || form.model.closed} + type="button"> + {_l("pollType_" + type)} + </button> + </span> + <div class="help-selected"> + <i class="fa fa-info-circle" aria-hidden="true"></i> + {_l("pollType_" + form.model.pollType + "_help")} + </div> + </div> + <div class="settings-options {with-default: form.creation}" show={form.model.pollType === "REGISTERED"}> + <div class="o-form-element"> + <Checkbox label="{_t.pollType_REGISTERED_limitEmail}" + disabled={form.model.closed} + checkboxchecked={limitEmail} + ontogglecheckbox={toggleLimitEmail}/> + </div> + <div show={limitEmail}> + <div class="o-form-element" each={suffix, index in form.model.emailAddressSuffixes}> + <div class="c-input-group"> + <div class="o-field"> + <input ref="suffix" + type="text" + class="c-field" + required + title={_t.pollType_REGISTERED_suffix} + name="suffix" + value="{suffix}" + disabled={form.model.closed} + placeholder="{_t.pollType_REGISTERED_suffixPlaceHolder}"/> + </div> + <button class="c-button c-button--error" + onclick={removeSuffix(index)} + disabled={form.model.closed || form.model.emailAddressSuffixes.length === 1} + title={_t.pollType_REGISTERED_suffix_remove}> + <i class="fa fa-times"></i> + </button> + </div> + </div> + <div class="actions-right"> + <button class="c-button c-button--success" + onclick={addSuffix} + title={_t.pollType_REGISTERED_suffix_add_title} + disabled={form.model.closed} + type="button"> + <i class="fa fa-plus"></i> + {_t.pollType_REGISTERED_suffix_add} + </button> + </div> + </div> </div> + </div> + <div class="form-section" show={form.creation}> <h4><i class="fa fa-cogs"></i> {_t.basic_usage}</h4> <div class="o-form-element"> <Checkbox label="{_t.use_basic_usage}" - disabled={opts.form.model.closed} + disabled={form.model.closed} checkboxchecked={!showOptions} ontogglecheckbox={toggleShowOptions}/> </div> @@ -72,7 +122,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; </h4> <div class="o-form-element select-or-radio"> <fieldset class="o-fieldset choice-radio" - disabled={form.hasVotes || opts.form.model.closed}> + disabled={form.hasVotes || form.model.closed}> <legend class="o-fieldset__legend">{_t.voteCountingType}</legend> <label each={type in form.voteCountingTypes} class="c-field c-field--choice" @@ -91,7 +141,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; tabindex="1" ref="voteCountingType" value={form.model.voteCountingType} - disabled={form.hasVotes || opts.form.model.closed} + disabled={form.hasVotes || form.model.closed} onchange={voteCountingTypeChanged}> <option each={type in form.voteCountingTypes} value={type.id} @@ -113,7 +163,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; name="choiceAddAllowed" id="choiceAddAllowed" ref="addChoices" - disabled={form.hasVotes || opts.form.model.closed} + disabled={form.hasVotes || form.model.closed} checkboxchecked={form.model.choiceAddAllowed} ontogglecheckbox={toggleChoiceAddAllowed}/> </div> @@ -127,7 +177,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; name="beginChoiceDate" id="beginChoiceDate" datetime={form.model.beginChoiceDate} - disabled={form.hasVotes || opts.form.model.closed}/> + disabled={form.hasVotes || form.model.closed}/> </div> <div class="o-form-element"> <label class="c-label" for="endChoiceDate"> @@ -138,24 +188,24 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; name="endChoiceDate" id="endChoiceDate" datetime={form.model.endChoiceDate} - disabled={form.hasVotes || opts.form.model.closed}/> + disabled={form.hasVotes || form.model.closed}/> </div> </div> - <MaxChoicesNumberConfig if={opts.form.model.voteCountingType == 1 || opts.form.model.voteCountingType == 4} + <MaxChoicesNumberConfig if={form.model.voteCountingType == 1 || form.model.voteCountingType == 4} ref="config" config={form.model.voteCountingConfig} count-choices={form.choices.length} disabled={form.hasVotes || form.model.closed}/> - <BordaConfig if={opts.form.model.voteCountingType == 5} + <BordaConfig if={form.model.voteCountingType == 5} ref="config" config={form.model.voteCountingConfig} count-choices={form.choices.length} disabled={form.hasVotes || form.model.closed}/> - <CumulativeConfig if={opts.form.model.voteCountingType == 2} + <CumulativeConfig if={form.model.voteCountingType == 2} ref="config" config={form.model.voteCountingConfig} disabled={form.hasVotes || form.model.closed}/> - <MajorityJudgmentConfig if={opts.form.model.voteCountingType == 8} + <MajorityJudgmentConfig if={form.model.voteCountingType == 8} ref="config" config={form.model.voteCountingConfig} disabled={form.hasVotes || form.model.closed}/> @@ -170,7 +220,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; name="votePeriod" id="votePeriod" ref="votePeriod" - disabled={opts.form.model.closed} + disabled={form.model.closed} checkboxchecked={form.model.votePeriod} ontogglecheckbox={toggleVotePeriod}/> </div> @@ -183,7 +233,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; tabindex="1" name="beginDate" id="beginDate" - disabled={form.hasVotes || opts.form.model.closed} + disabled={form.hasVotes || form.model.closed} datetime={form.model.beginDate}/> </div> <div class="o-form-element"> @@ -196,7 +246,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; id="endDate" datetime={form.model.endDate} min={refs.beginDate.value} - disabled={opts.form.model.closed} + disabled={form.model.closed} onchange="{onEndDateChanged}"/> </div> </div> @@ -207,7 +257,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; <select class="c-field c-field--label" tabindex="1" ref="voteVisibility" - disabled={form.hasVotes || opts.form.model.closed} + disabled={form.hasVotes || form.model.closed} value={form.model.voteVisibility}> <option value="ANONYMOUS">{_t.voteVisibility_anonymous}</option> <option value="CREATOR">{_t.voteVisibility_creator}</option> @@ -220,7 +270,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; name="anonymousVote" id="anonymousVote" ref="anonymousVote" - disabled={form.hasVotes || opts.form.model.closed} + disabled={form.hasVotes || form.model.closed} checkboxchecked={form.model.anonymousVoteAllowed} ontogglecheckbox={toggleAnonymousVote}/> </div> @@ -234,7 +284,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; <select class="c-field c-field--label" tabindex="1" ref="resultVisibility" - disabled={form.hasVotes || opts.form.model.closed} + disabled={form.hasVotes || form.model.closed} value={form.model.resultVisibility}> <option value="CREATOR">{_t.resultVisibility_creator}</option> <option value="VOTER">{_t.resultVisibility_voter}</option> @@ -246,7 +296,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; name="continuousResults" id="continuousResults" ref="continuousResults" - disabled={opts.form.model.closed} + disabled={form.model.closed} checkboxchecked={form.model.continuousResults} ontogglecheckbox={toggleContinuousResults}/> </div> @@ -263,7 +313,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; <select class="c-field c-field--label" tabindex="1" ref="commentVisibility" - disabled={form.hasVotes || opts.form.model.closed} + disabled={form.hasVotes || form.model.closed} value={form.model.commentVisibility}> <option value="CREATOR">{_t.commentVisibility_creator}</option> <option value="VOTER">{_t.commentVisibility_voter}</option> @@ -282,24 +332,24 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; <Checkbox label="{_t.voteNotification}" labelclass="c-field c-field--choice" ref="voteNotification" - disabled={!form.model.creatorEmail || opts.form.model.closed} + disabled={!form.model.creatorEmail || form.model.closed} checkboxchecked={form.model.voteNotification}/> <Checkbox label="{_t.commentNotification}" labelclass="c-field c-field--choice" ref="commentNotification" - disabled={!form.model.creatorEmail || opts.form.model.closed} + disabled={!form.model.creatorEmail || form.model.closed} checkboxchecked={form.model.commentNotification}/> <Checkbox label="{_t.newChoiceNotification}" labelclass="c-field c-field--choice" ref="newChoiceNotification" - disabled={!form.model.creatorEmail || opts.form.model.closed} + disabled={!form.model.creatorEmail || form.model.closed} checkboxchecked={form.model.newChoiceNotification}/> </fieldset> <div class="o-form-element"> <Checkbox label="{_t.notifyMeBeforePollEnds}" labelclass="c-field c-field--choice" i18nprefix="{i18nprefix}" - disabled={disableNotifyMeBeforePollEnds || opts.form.model.closed} + disabled={disableNotifyMeBeforePollEnds || form.model.closed} checkboxchecked={notifyMeBeforePollEnds} ontogglecheckbox={toggleNotifyMeBeforePollEnds}> <i class="fa fa-question-circle cursor-help warning" @@ -316,7 +366,7 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; ref="notifyMeHoursBeforePollEnds" value={form.model.notifyMeHoursBeforePollEnds} min="{notifyMeBeforePollEnds ? 1 : 0}" - disabled={opts.form.model.closed}> + disabled={form.model.closed}> </div> </div> </div> @@ -341,12 +391,13 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; this.form.model.votePeriod = this.form.model.beginDate || this.form.model.endDate; this.notifyMeBeforePollEnds = this.form.model.creatorEmail && this.form.model.notifyMeHoursBeforePollEnds > 0; this.disableNotifyMeBeforePollEnds = !this.form.model.creatorEmail || !this.form.model.votePeriod || !this.form.model.endDate; + this.limitEmail = this.form.model.emailAddressSuffixes && this.form.model.emailAddressSuffixes.length; this.on("mount", () => { this.refs["voteCountingType" + this.form.model.voteCountingType].checked = true; this.refs.voteCountingType.value = this.form.model.voteCountingType; this.updateVoteCountingTypeHelp(); - this.refs.pollType.focus(); + this.refs["pollType_" + this.form.model.pollType].focus(); }); this.toggleVoteCountingTypeHelp = () => { @@ -434,11 +485,29 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; this.update(); }; - this.togglePollType = () => { - this.opts.form.togglePollType(); + this.pollTypeChange = newType => () => { + this.form.setPollType(newType); this.update(); }; + this.toggleLimitEmail = () => { + this.limitEmail = !this.limitEmail; + if (this.limitEmail && !this.form.model.emailAddressSuffixes) { + this.form.model.emailAddressSuffixes = [""]; + } + this.update(); + }; + + this.removeSuffix = index => () => { + if (this.form.model.emailAddressSuffixes.length > 1) { + this.form.model.emailAddressSuffixes.splice(index, 1); + } + }; + + this.addSuffix = () => { + this.form.model.emailAddressSuffixes.push(""); + }; + this.submit = () => { if (!this.showOptions && this.form.creation) { @@ -470,6 +539,13 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; this.form.model.notifyMeHoursBeforePollEnds = 0; } } + if (this.form.model.pollType === "REGISTERED" && this.limitEmail) { + this.form.model.emailAddressSuffixes = + (Array.isArray(this.refs.suffix) ? this.refs.suffix : [this.refs.suffix]) + .map(suffixInput => suffixInput.value); + } else { + this.form.model.emailAddressSuffixes = null; + } }; this.listen("locale", () => { @@ -526,6 +602,10 @@ import "../voteCountingType/MajorityJudgmentConfig.tag.html"; padding: 0.5em 0; } + .o-form-element .c-input-group { + margin: 0.5em 0; + } + .settings-options .form-section { margin: 1.5em 0; } diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/Summary.tag.html b/pollen-ui-riot-js/src/main/web/tag/poll/Summary.tag.html index 01ae48a3..a933ad72 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/Summary.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/poll/Summary.tag.html @@ -117,8 +117,20 @@ </div> </div> + <div if={opts.form.model.pollType === "REGISTERED"} class="summary-part"> + <h3>{_t.members}</h3> + <div> + {opts.form.model.emailAddressSuffixes && opts.form.model.emailAddressSuffixes.length ? _t.registered_filter : _t.registered_all} + <ul class="suffixes"> + <li each={suffix in opts.form.model.emailAddressSuffixes}> + *{suffix} + </li> + </ul> + </div> + </div> + <div class="summary-part"> - <a if={opts.form.model.pollType === "FREE"} + <a if={opts.form.model.pollType !== "RESTRICTED"} href="{voteUrl}" class="c-button c-button--info"> <i class="link fa fa-envelope-o"/> @@ -258,5 +270,9 @@ font-size: 1em; } + ul.suffixes { + margin-left: 2em; + } + </style> </Summary> diff --git a/pollen-votecounting-condorcet/src/main/resources/i18n/pollen-votecounting-condorcet_en_GB.properties b/pollen-votecounting-condorcet/src/main/resources/i18n/pollen-votecounting-condorcet_en_GB.properties index 6fd2e4da..06afd681 100644 --- a/pollen-votecounting-condorcet/src/main/resources/i18n/pollen-votecounting-condorcet_en_GB.properties +++ b/pollen-votecounting-condorcet/src/main/resources/i18n/pollen-votecounting-condorcet_en_GB.properties @@ -1,5 +1,5 @@ pollen.voteCountingType.condorcet=Condorcet -pollen.voteCountingType.condorcet.help=Rank choices by preference order from 1 to N (1=favorite).<br/>Only the rank is taken into account, not the values. Two choices can have the same value.<br/>In this method, the winner is the choice which wins the most duels against the other choices.<br/>More about this method: <a href='http://en.wikipedia.org/wiki/Condorcet_method' target='#doc'>http://en.wikipedia.org/wiki/Condorcet_method</a> +pollen.voteCountingType.condorcet.help=Rank choices by preference order from 1 to N (1\=favorite).<br/>Only the rank is taken into account, not the values. Two choices can have the same value.<br/>In this method, the winner is the choice which wins the most duels against the other choices.<br/>More about this method\: <a href\='http\://en.wikipedia.org/wiki/Condorcet_method' target\='\#doc'>http\://en.wikipedia.org/wiki/Condorcet_method</a> pollen.voteCountingType.condorcet.shortHelp=Rank choices by preference order from 1 to N (1\=favorite). pollen.voteCountingType.condorcet.voteValue.error.positive=The value %1$s must be positive. pollen.voteCountingType.condorcet.voteValue.error.required=The value is required. diff --git a/pollen-votecounting-condorcet/src/main/resources/i18n/pollen-votecounting-condorcet_fr_FR.properties b/pollen-votecounting-condorcet/src/main/resources/i18n/pollen-votecounting-condorcet_fr_FR.properties index b790dbb8..b748948c 100644 --- a/pollen-votecounting-condorcet/src/main/resources/i18n/pollen-votecounting-condorcet_fr_FR.properties +++ b/pollen-votecounting-condorcet/src/main/resources/i18n/pollen-votecounting-condorcet_fr_FR.properties @@ -1,5 +1,5 @@ pollen.voteCountingType.condorcet=Condorcet -pollen.voteCountingType.condorcet.help=Classer les choix par ordre de préférence de 1 à N (1=préféré).<br/>Seul l'ordre des choix compte, peu importe les valeurs. Deux choix peuvent avoir la même valeur.<br/>Dans cette méthode, le gagnant est celui qui remporte le plus de duels par rapport aux autres choix.<br/>Pour en savoir plus : <a href='http://fr.wikipedia.org/wiki/M%C3%A9thode_Condorcet' target='#doc'>http://fr.wikipedia.org/wiki/Méthode_Condorcet</a> +pollen.voteCountingType.condorcet.help=Classer les choix par ordre de préférence de 1 à N (1\=préféré).<br/>Seul l'ordre des choix compte, peu importe les valeurs. Deux choix peuvent avoir la même valeur.<br/>Dans cette méthode, le gagnant est celui qui remporte le plus de duels par rapport aux autres choix.<br/>Pour en savoir plus \: <a href\='http\://fr.wikipedia.org/wiki/M%C3%A9thode_Condorcet' target\='\#doc'>http\://fr.wikipedia.org/wiki/Méthode_Condorcet</a> pollen.voteCountingType.condorcet.shortHelp=Classer les choix par ordre de préférence de 1 à N (1\=préféré). pollen.voteCountingType.condorcet.voteValue.error.positive=La valeur du vote %1$s doit être positive. pollen.voteCountingType.condorcet.voteValue.error.required=La valeur du vote est obligatoire. -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.