From eaec337960e72517e3ab0a374d9d4310ea6ac9b7 Mon Sep 17 00:00:00 2001 From: Sebin Song Date: Fri, 14 Feb 2025 04:40:57 +0900 Subject: [PATCH] #2603 - Character limit for poll option and question (#2622) * implement max-char for poll options in chatroom.js contract * add maxlength html attrs * display char-len-indicator component when needed --- frontend/model/contracts/chatroom.js | 18 ++- frontend/model/contracts/shared/constants.js | 2 + .../views/containers/chatroom/CreatePoll.vue | 109 ++++++++++++++---- 3 files changed, 103 insertions(+), 26 deletions(-) diff --git a/frontend/model/contracts/chatroom.js b/frontend/model/contracts/chatroom.js index dc70653909..a720a1c330 100644 --- a/frontend/model/contracts/chatroom.js +++ b/frontend/model/contracts/chatroom.js @@ -20,7 +20,9 @@ import { MESSAGE_NOTIFICATIONS, MESSAGE_RECEIVE_RAW, MESSAGE_TYPES, - POLL_STATUS + POLL_STATUS, + POLL_OPTION_MAX_CHARS, + POLL_QUESTION_MAX_CHARS } from './shared/constants.js' import { createMessage, @@ -303,7 +305,19 @@ sbp('chelonia/defineContract', { } }, 'gi.contracts/chatroom/addMessage': { - validate: actionRequireInnerSignature(messageType), + validate: (data, props) => { + actionRequireInnerSignature(messageType)(data, props) + + if (data.type === MESSAGE_TYPES.POLL) { + const optionStrings = data.pollData.options.map(o => o.value) + if (data.pollData.question.length > POLL_QUESTION_MAX_CHARS) { + throw new TypeError(L('Poll question must be less than {n} characters', { n: POLL_QUESTION_MAX_CHARS })) + } + if (optionStrings.some(v => v.length > POLL_OPTION_MAX_CHARS)) { + throw new TypeError(L('Poll option must be less than {n} characters', { n: POLL_OPTION_MAX_CHARS })) + } + } + }, // NOTE: This function is 'reentrant' and may be called multiple times // for the same message and state. The `direction` attributes handles // these situations especially, and it's meant to mark sent-by-the-user diff --git a/frontend/model/contracts/shared/constants.js b/frontend/model/contracts/shared/constants.js index 63518fb452..453885b50c 100644 --- a/frontend/model/contracts/shared/constants.js +++ b/frontend/model/contracts/shared/constants.js @@ -132,3 +132,5 @@ export const POLL_STATUS = { } export const POLL_MAX_OPTIONS = 20 +export const POLL_QUESTION_MAX_CHARS = 280 +export const POLL_OPTION_MAX_CHARS = 280 diff --git a/frontend/views/containers/chatroom/CreatePoll.vue b/frontend/views/containers/chatroom/CreatePoll.vue index 0ed1fb14ac..b6c81ed279 100644 --- a/frontend/views/containers/chatroom/CreatePoll.vue +++ b/frontend/views/containers/chatroom/CreatePoll.vue @@ -14,10 +14,11 @@ section.c-body form.c-form(@submit.prevent='' :disabled='form.disabled') .field(data-test='question') - input.input.c-input( + input.input.c-input.c-question-input( name='question' ref='question' :placeholder='L("Ask a question!")' + :maxlength='config.questionMaxChars' @input='e => debounceField("question", e.target.value)' @blur='e => updateField("question", e.target.value)' :class='{ error: $v.form.question.$error }' @@ -25,6 +26,13 @@ v-error:question='' ) + char-length-indicator.c-for-question( + v-if='form.question' + :current-length='form.question.length || 0' + :max='config.questionMaxChars' + :error='$v.form.question.$error' + ) + .field.c-add-options(data-test='options') i18n.label Add options @@ -32,13 +40,15 @@ fieldset.inputgroup.c-option-item( v-for='(option, index) in form.options' :key='option.id' + :class='{ "has-value": option.value.length > 0 }' ) - input.input.c-input( + input.input.c-input.c-option-input( type='text' :aria-label='L("Option value")' :ref='"input" + option.id' :placeholder='optionPlaceholder(index + 1)' v-model.trim='option.value' + :maxlength='config.optionMaxChars' ) button.is-icon-small.is-btn-shifted( type='button' @@ -46,6 +56,11 @@ @click='removeOption(option.id)' ) i.icon-times + char-length-indicator.c-for-option( + v-if='option.value' + :current-length='option.value.length || 0' + :max='config.optionMaxChars' + ) button.link.has-icon( v-if='enableMoreButton' @@ -82,6 +97,8 @@ i.icon-exclamation-triangle i18n Note: it is possible for a group member to "hack" the app to figure out who voted on what. + banner-scoped(ref='formMsg' allow-a) + .buttons.c-btns-container(:class='{ "is-vertical": ephemeral.isDesktopScreen }') i18n.is-outlined( :class='{ "is-small": ephemeral.isDesktopScreen }' @@ -102,15 +119,17 @@