diff --git a/contracts/evoting/types/election.go b/contracts/evoting/types/election.go index 50bbd1c1..bc2d4138 100644 --- a/contracts/evoting/types/election.go +++ b/contracts/evoting/types/election.go @@ -333,8 +333,9 @@ type ShuffleInstance struct { // Configuration contains the configuration of a new poll. type Configuration struct { - Title Title - Scaffold []Subject + Title Title + Scaffold []Subject + AdditionalInfo string } // MaxBallotSize returns the maximum number of bytes required to store a ballot diff --git a/integration/performance_test.go b/integration/performance_test.go index c896fb83..9d4e542a 100644 --- a/integration/performance_test.go +++ b/integration/performance_test.go @@ -214,6 +214,7 @@ func createFormNChunks(m txManager, title types.Title, admin string, numChunks i }}, }, }, + AdditionalInfo: "", } createForm := types.CreateForm{ diff --git a/internal/testing/fake/election.go b/internal/testing/fake/election.go index 489dae46..a84e2315 100644 --- a/internal/testing/fake/election.go +++ b/internal/testing/fake/election.go @@ -26,6 +26,7 @@ func NewForm(ctx serde.Context, snapshot store.Snapshot, formID string) (types.F De: "", URL: "", }, + AdditionalInfo: "", }, FormID: formID, Status: types.Closed, diff --git a/web/frontend/package-lock.json b/web/frontend/package-lock.json index f4dcc529..90aebf87 100644 --- a/web/frontend/package-lock.json +++ b/web/frontend/package-lock.json @@ -14,6 +14,7 @@ "@tailwindcss/typography": "^0.5.2", "ajv": "^8.11.0", "buffer": "^6.0.3", + "dompurify": "^3.0.9", "file-saver": "^2.0.5", "i18next": "^21.6.10", "i18next-browser-languagedetector": "^6.1.3", @@ -6701,6 +6702,11 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.9.tgz", + "integrity": "sha512-uyb4NDIvQ3hRn6NiC+SIFaP4mJ/MdXlvtunaqK9Bn6dD3RuB/1S/gasEjDHD8eiaqdSael2vBv+hOs7Y+jhYOQ==" + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -21654,6 +21660,11 @@ "domelementtype": "^2.2.0" } }, + "dompurify": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.9.tgz", + "integrity": "sha512-uyb4NDIvQ3hRn6NiC+SIFaP4mJ/MdXlvtunaqK9Bn6dD3RuB/1S/gasEjDHD8eiaqdSael2vBv+hOs7Y+jhYOQ==" + }, "domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", diff --git a/web/frontend/package.json b/web/frontend/package.json index 88651460..e0be0ff7 100644 --- a/web/frontend/package.json +++ b/web/frontend/package.json @@ -20,6 +20,7 @@ "@tailwindcss/typography": "^0.5.2", "ajv": "^8.11.0", "buffer": "^6.0.3", + "dompurify": "^3.0.9", "file-saver": "^2.0.5", "i18next": "^21.6.10", "i18next-browser-languagedetector": "^6.1.3", diff --git a/web/frontend/src/language/de.json b/web/frontend/src/language/de.json index 30e6f332..a14cc8db 100644 --- a/web/frontend/src/language/de.json +++ b/web/frontend/src/language/de.json @@ -38,6 +38,7 @@ "subject": "Betreff", "choices": "Auswahlmöglichkeiten", "url": "URL", + "additionalInfo": "Weitere Informationen", "answers": "Antworten", "enterMaxLength": "Geben Sie die MaxLength", "maxChoices": "Maximale Anzahl von Auswahlmöglichkeiten", diff --git a/web/frontend/src/language/en.json b/web/frontend/src/language/en.json index ac964caf..49cd1dce 100644 --- a/web/frontend/src/language/en.json +++ b/web/frontend/src/language/en.json @@ -34,6 +34,7 @@ "subject": "Subject", "choices": "Choices", "url": "URL", + "additionalInfo": "Additional information", "answers": "Answers", "enterMaxLength": "Enter the MaxLength", "maxChoices": "Max number of choices", diff --git a/web/frontend/src/language/fr.json b/web/frontend/src/language/fr.json index d0ad1996..4971d4ba 100644 --- a/web/frontend/src/language/fr.json +++ b/web/frontend/src/language/fr.json @@ -38,6 +38,7 @@ "subject": "Sujet", "choices": "Choix", "url": "URL", + "additionalInfo": "Informations supplémentaires", "answers": "Réponses", "enterMaxLength": "Entrer la longueur max", "maxChoices": "Max nombre de choix", diff --git a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx index e34a92a1..f3b9c6d6 100644 --- a/web/frontend/src/pages/ballot/components/BallotDisplay.tsx +++ b/web/frontend/src/pages/ballot/components/BallotDisplay.tsx @@ -6,6 +6,7 @@ import Select from './Select'; import Text from './Text'; import { DragDropContext } from 'react-beautiful-dnd'; import { internationalize, urlizeLabel } from './../../utils'; +import DOMPurify from 'dompurify'; type BallotDisplayProps = { configuration: Configuration; @@ -82,6 +83,13 @@ const BallotDisplay: FC = ({

{urlizeLabel(internationalize(language, titles), titles.URL)}

+
{configuration.Scaffold.map((subject: types.Subject) => SubjectTree(subject))}
{userErrors}
diff --git a/web/frontend/src/pages/form/Result.tsx b/web/frontend/src/pages/form/Result.tsx index 826c294e..33a1608c 100644 --- a/web/frontend/src/pages/form/Result.tsx +++ b/web/frontend/src/pages/form/Result.tsx @@ -13,6 +13,7 @@ import IndividualResult from './IndividualResult'; import { default as i18n } from 'i18next'; import GroupedResult from './GroupedResult'; import { internationalize, urlizeLabel } from './../utils'; +import DOMPurify from 'dompurify'; // Functional component that displays the result of the votes const FormResult: FC = () => { @@ -103,6 +104,13 @@ const FormResult: FC = () => { configuration.Title.URL )} +
diff --git a/web/frontend/src/pages/form/Show.tsx b/web/frontend/src/pages/form/Show.tsx index 08e6c207..ee9032a9 100644 --- a/web/frontend/src/pages/form/Show.tsx +++ b/web/frontend/src/pages/form/Show.tsx @@ -17,6 +17,7 @@ import DKGStatusTable from './components/DKGStatusTable'; import LoadingButton from './components/LoadingButton'; import { internationalize, urlizeLabel } from './../utils'; import { default as i18n } from 'i18next'; +import DOMPurify from 'dompurify'; const FormShow: FC = () => { const { t } = useTranslation(); @@ -225,6 +226,13 @@ const FormShow: FC = () => {
{urlizeLabel(internationalize(i18n.language, titles), titles.URL)}
+
Form ID : {formId}
{status >= Status.Open && diff --git a/web/frontend/src/pages/form/components/FormForm.tsx b/web/frontend/src/pages/form/components/FormForm.tsx index 2ec61841..ccadaf71 100644 --- a/web/frontend/src/pages/form/components/FormForm.tsx +++ b/web/frontend/src/pages/form/components/FormForm.tsx @@ -29,6 +29,7 @@ import { FlashContext, FlashLevel } from 'index'; import { availableLanguages } from 'language/Configuration'; import LanguageButtons from 'language/LanguageButtons'; import { default as i18n } from 'i18next'; +import DOMPurify from 'dompurify'; // notifyParent must be used by the child to tell the parent if the subject's // schema changed. @@ -55,7 +56,7 @@ const FormForm: FC = () => { const [marshalledConf, setMarshalledConf] = useState(marshalConfig(conf)); const { configuration: previewConf, answers, setAnswers } = useConfiguration(marshalledConf); - const { Title, Scaffold } = conf; + const { Title, Scaffold, AdditionalInfo } = conf; const [language, setLanguage] = useState(i18n.language); const regexPattern = /[^a-zA-Z0-9]/g; @@ -229,6 +230,19 @@ const FormForm: FC = () => { placeholder={t('url')} className="m-3 px-1 w-100 text-lg border rounded-md" /> + + setConf({ + ...conf, + AdditionalInfo: e.target.value, + }) + } + name="AdditionalInfo" + type="text" + placeholder={t('additionalInfo')} + className="m-3 px-1 w-100 text-lg border rounded-md" + />
+