From b9ff88f41ca6fe252f502b355247bd53456bbdf8 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Tue, 29 Jun 2021 14:47:28 +0200 Subject: [PATCH] fix(communities): Naming validation for communities Channel, channels category name and community name handled according to the new validation. Fixes: #2297 --- .../CreateCategoryPopup.qml | 46 +++----- .../CreateChannelPopup.qml | 91 ++++++-------- .../CreateCommunityPopup.qml | 111 +++++++----------- ui/imports/Utils.qml | 51 ++++++++ ui/shared/Input.qml | 1 + 5 files changed, 151 insertions(+), 149 deletions(-) diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/CreateCategoryPopup.qml b/ui/app/AppLayouts/Chat/CommunityComponents/CreateCategoryPopup.qml index 62ad80a713..ad829086e6 100644 --- a/ui/app/AppLayouts/Chat/CommunityComponents/CreateCategoryPopup.qml +++ b/ui/app/AppLayouts/Chat/CommunityComponents/CreateCategoryPopup.qml @@ -13,29 +13,29 @@ ModalPopup { property var channels: [] property bool isEdit: false - readonly property int maxDescChars: 140 - property string nameValidationError: "" - property bool isValid: nameInput.isValid + readonly property int maxCategoryNameLength: 30 + readonly property var categoryNameValidator: Utils.Validate.NoEmpty + | Utils.Validate.TextLength + | Utils.Validate.TextLowercase id: popup height: 453 onOpened: { - nameInput.text = isEdit ? categoryName : ""; if(isEdit){ + nameInput.text = categoryName channels = JSON.parse(chatsModel.communities.activeCommunity.getChatIdsByCategory(categoryId)) } nameInput.forceActiveFocus(Qt.MouseFocusReason) - if(isEdit){ - validate(); - } } onClosed: destroy() - function validate() { - nameInput.validate() - return isValid + function isFormValid() { + return Utils.validateAndReturnError(nameInput.text, + categoryNameValidator, + qsTr("category name"), + maxCategoryNameLength) === "" } title: isEdit ? @@ -65,25 +65,15 @@ ModalPopup { Input { id: nameInput placeholderText: qsTr("Category title") - validationError: popup.nameValidationError - - property bool isValid: false + maxLength: maxCategoryNameLength onTextEdited: { - validate() - } + text = Utils.convertSpacesToDashesAndUpperToLowerCase(text); - function validate() { - validationError = "" - if (nameInput.text === "") { - //% "You need to enter a name" - validationError = qsTrId("you-need-to-enter-a-name") - } else if (nameInput.text.length > 100) { - //% "Your name needs to be 100 characters or shorter" - validationError = qsTrId("your-name-needs-to-be-100-characters-or-shorter") - } - isValid = validationError === "" - return validationError + validationError = Utils.validateAndReturnError(text, + categoryNameValidator, + qsTr("category name"), + maxCategoryNameLength) } } @@ -213,13 +203,13 @@ ModalPopup { } footer: StatusButton { - enabled: popup.isValid + enabled: isFormValid() text: isEdit ? qsTr("Save") : qsTr("Create") anchors.right: parent.right onClicked: { - if (!validate()) { + if (!isFormValid()) { scrollView.scrollBackUp() return } diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/CreateChannelPopup.qml b/ui/app/AppLayouts/Chat/CommunityComponents/CreateChannelPopup.qml index 62c35d408d..038df365bd 100644 --- a/ui/app/AppLayouts/Chat/CommunityComponents/CreateChannelPopup.qml +++ b/ui/app/AppLayouts/Chat/CommunityComponents/CreateChannelPopup.qml @@ -12,15 +12,18 @@ ModalPopup { property QtObject channel property bool isEdit: false property string categoryId: "" - readonly property int maxDescChars: 140 - property string nameValidationError: "" + + readonly property int maxChannelNameLength: 30 + readonly property var channelNameValidator: Utils.Validate.NoEmpty + | Utils.Validate.TextLength + | Utils.Validate.TextLowercaseLettersNumberAndDashes + + readonly property int maxChannelDescLength: 140 + readonly property var channelDescValidator: Utils.Validate.NoEmpty + | Utils.Validate.TextLength property Component pinnedMessagesPopupComponent - property bool isValid: - nameInput.isValid && - descriptionTextArea.isValid - id: popup height: 475 @@ -37,10 +40,15 @@ ModalPopup { } onClosed: destroy() - function validate() { - nameInput.validate() - descriptionTextArea.validate() - return isValid + function isFormValid() { + return Utils.validateAndReturnError(nameInput.text, + channelNameValidator, + qsTr("channel name"), + maxChannelNameLength) === "" + && Utils.validateAndReturnError(descriptionTextArea.text, + channelDescValidator, + qsTr("channel decription"), + maxChannelDescLength) === "" } //% "New channel" @@ -70,30 +78,15 @@ ModalPopup { id: nameInput //% "A cool name" placeholderText: qsTrId("a-cool-name") - validationError: popup.nameValidationError - - property bool isValid: false || isEdit + maxLength: popup.maxChannelNameLength onTextEdited: { - if (text.includes(" ")) { - text = text.replace(" ", "-") - } - validate() - } + text = Utils.convertSpacesToDashesAndUpperToLowerCase(text); - function validate() { - validationError = "" - if (nameInput.text === "") { - //% "You need to enter a name" - validationError = qsTrId("you-need-to-enter-a-name") - } else if (!(/^[a-z0-9\-]+$/.test(nameInput.text))) { - validationError = qsTr("Use only lowercase letters (a to z), numbers & dashes (-). Do not use chat keys.") - } else if (nameInput.text.length > 100) { - //% "Your name needs to be 100 characters or shorter" - validationError = qsTrId("your-name-needs-to-be-100-characters-or-shorter") - } - isValid = validationError === "" - return validationError + validationError = Utils.validateAndReturnError(text, + channelNameValidator, + qsTr("channel name"), + maxChannelNameLength) } } @@ -103,36 +96,28 @@ ModalPopup { label: qsTrId("channel-description") //% "What your channel is about" placeholderText: qsTrId("what-your-channel-is-about") - //% "The description cannot exceed %1 characters" - validationError: descriptionTextArea.text.length > popup.maxDescChars ? qsTrId("the-description-cannot-exceed-140-characters") : - popup.descriptionValidationError || "" + anchors.top: nameInput.bottom anchors.topMargin: Style.current.bigPadding customHeight: 88 - property bool isValid: false || isEdit - onTextChanged: validate() - - function resetValidation() { - isValid = false - validationError = "" - } - - function validate() { - validationError = "" - if (text.length > popup.maxDescChars) { - validationError = qsTrId("the-description-cannot-exceed-140-characters") + onTextChanged: { + if(text.length > maxChannelDescLength) + { + textField.remove(maxChannelDescLength, text.length) + return } - if (text === "") { - validationError = qsTr("You need to enter a description") - } - isValid = validationError === "" + + validationError = Utils.validateAndReturnError(text, + channelDescValidator, + qsTr("channel decription"), + maxChannelDescLength) } } StyledText { id: charLimit - text: `${descriptionTextArea.text.length}/${maxDescChars}` + text: `${descriptionTextArea.text.length}/${maxChannelDescLength}` anchors.top: descriptionTextArea.bottom anchors.topMargin: !descriptionTextArea.validationError ? 5 : - Style.current.smallPadding anchors.right: descriptionTextArea.right @@ -223,14 +208,14 @@ ModalPopup { } footer: StatusButton { - enabled: popup.isValid + enabled: isFormValid() text: isEdit ? qsTr("Save") : //% "Create" qsTrId("create") anchors.right: parent.right onClicked: { - if (!validate()) { + if (!isFormValid()) { scrollView.scrollBackUp() return } diff --git a/ui/app/AppLayouts/Chat/CommunityComponents/CreateCommunityPopup.qml b/ui/app/AppLayouts/Chat/CommunityComponents/CreateCommunityPopup.qml index ee3724062b..a3c355ce84 100644 --- a/ui/app/AppLayouts/Chat/CommunityComponents/CreateCommunityPopup.qml +++ b/ui/app/AppLayouts/Chat/CommunityComponents/CreateCommunityPopup.qml @@ -7,15 +7,20 @@ import "../../../../shared" import "../../../../shared/status" ModalPopup { - readonly property int maxDescChars: 140 - property QtObject community: chatsModel.communities.activeCommunity property bool isEdit: false - property bool isValid: - nameInput.isValid && - descriptionTextArea.isValid && - colorPicker.isValid + + readonly property int maxCommunityNameLength: 30 + readonly property var communityNameValidator: Utils.Validate.NoEmpty + | Utils.Validate.TextLength + + readonly property int maxCommunityDescLength: 140 + readonly property var communityDescValidator: Utils.Validate.NoEmpty + | Utils.Validate.TextLength + + readonly property var communityColorValidator: Utils.Validate.NoEmpty + | Utils.Validate.TextHexColor id: popup height: 600 @@ -34,11 +39,17 @@ ModalPopup { } onClosed: destroy() - function validate() { - nameInput.validate() - descriptionTextArea.validate() - colorPicker.validate() - return isValid + function isFormValid() { + return Utils.validateAndReturnError(nameInput.text, + communityNameValidator, + qsTr("community name"), + maxCommunityNameLength) === "" + && Utils.validateAndReturnError(descriptionTextArea.text, + communityDescValidator, + qsTr("community decription"), + maxCommunityDescLength) === "" + && Utils.validateAndReturnError(colorPicker.text, + communityColorValidator) === "" } title: isEdit ? @@ -78,28 +89,13 @@ ModalPopup { label: qsTrId("name-your-community") //% "A catchy name" placeholderText: qsTrId("name-your-community-placeholder") - property bool isValid: false + maxLength: maxCommunityNameLength onTextEdited: { - if (text.includes(" ")) { - text = text.replace(" ", "-") - } - validate() - } - - function validate() { - validationError = "" - if (nameInput.text === "") { - //% "You need to enter a name" - validationError = qsTrId("you-need-to-enter-a-name") - } else if (!(/^[a-z0-9\-]+$/.test(nameInput.text))) { - validationError = qsTr("Use only lowercase letters (a to z), numbers & dashes (-). Do not use chat keys.") - } else if (nameInput.text.length > 100) { - //% "Your name needs to be 100 characters or shorter" - validationError = qsTrId("your-name-needs-to-be-100-characters-or-shorter") - } - isValid = validationError === "" - return validationError + validationError = Utils.validateAndReturnError(text, + communityNameValidator, + qsTr("community name"), + maxCommunityNameLength) } } @@ -109,37 +105,29 @@ ModalPopup { label: qsTrId("give-a-short-description-community") //% "What your community is about" placeholderText: qsTrId("what-your-community-is-about") - //% "The description cannot exceed 140 characters" - validationError: descriptionTextArea.text.length > popup.maxDescChars ? qsTrId("the-description-cannot-exceed-140-characters") : - popup.descriptionValidationError || "" + anchors.top: nameInput.bottom anchors.topMargin: Style.current.bigPadding customHeight: 88 textField.wrapMode: TextEdit.Wrap - property bool isValid: false - onTextChanged: validate() - - function resetValidation() { - isValid = false - validationError = "" - } - - function validate() { - validationError = "" - if (text.length > popup.maxDescChars) { - validationError = qsTrId("the-description-cannot-exceed-140-characters") + onTextChanged: { + if(text.length > maxCommunityDescLength) + { + textField.remove(maxCommunityDescLength, text.length) + return } - if (text === "") { - validationError = qsTr("You need to enter a description") - } - isValid = validationError === "" + + validationError = Utils.validateAndReturnError(text, + communityDescValidator, + qsTr("community decription"), + maxCommunityDescLength) } } StyledText { id: charLimit - text: `${descriptionTextArea.text.length}/${popup.maxDescChars}` + text: `${descriptionTextArea.text.length}/${maxCommunityDescLength}` anchors.top: descriptionTextArea.bottom anchors.topMargin: !descriptionTextArea.validationError ? 5 : - Style.current.smallPadding anchors.right: descriptionTextArea.right @@ -285,7 +273,6 @@ ModalPopup { Input { property string defaultColor: Style.current.blue - property bool isValid: true id: colorPicker label: qsTr("Community color") @@ -295,21 +282,8 @@ ModalPopup { textField.text: defaultColor textField.onReleased: colorDialog.open() - onTextChanged: validate() - - function resetValidation() { - isValid = true - validationError = "" - } - - function validate() { - validationError = "" - if (text === "") { - validationError = qsTr("Please enter a color") - } else if (!Utils.isHexColor(colorPicker.text)) { - validationError = qsTr("Must be an hexadecimal color (eg: #4360DF)") - } - isValid = validationError === "" + onTextChanged: { + validationError = Utils.validateAndReturnError(text, communityColorValidator) } StatusIconButton { @@ -447,6 +421,7 @@ ModalPopup { } StatusButton { id: btnCreateEdit + enabled: isFormValid() text: isEdit ? //% "Save" qsTrId("Save") : @@ -454,7 +429,7 @@ ModalPopup { qsTrId("create") anchors.right: parent.right onClicked: { - if (!validate()) { + if (!isFormValid()) { scrollView.scrollBackUp() return } diff --git a/ui/imports/Utils.qml b/ui/imports/Utils.qml index e2ecfb3304..a716213ff7 100644 --- a/ui/imports/Utils.qml +++ b/ui/imports/Utils.qml @@ -556,6 +556,57 @@ QtObject { return Array.from(new Set(array)) } + function hasUpperCaseLetter(str) { + return (/[A-Z]/.test(str)) + } + + function convertSpacesToDashesAndUpperToLowerCase(str) + { + if (str.includes(" ")) + str = str.replace(/ /g, "-") + + if(hasUpperCaseLetter(str)) + str = str.toLowerCase() + + return str + } + + /* Validation section start */ + + enum Validate { + NoEmpty = 0x01, + TextLength = 0x02, + TextHexColor = 0x04, + TextLowercaseLettersNumberAndDashes = 0x08 + } + + function validateAndReturnError(str, validation, fieldName = "field", limit = 0) + { + let errMsg = "" + + if(validation & Utils.Validate.NoEmpty && str === "") { + errMsg = qsTr("You need to enter a %1").arg(fieldName) + } + + if(validation & Utils.Validate.TextLength && str.length > limit) { + errMsg = qsTr("The %1 cannot exceed %2 characters").arg(fieldName, limit) + } + + if(validation & Utils.Validate.TextHexColor && !isHexColor(str)) { + errMsg = qsTr("Must be an hexadecimal color (eg: #4360DF)") + } + + if(validation & Utils.Validate.TextLowercaseLettersNumberAndDashes && !isValidChannelName(str)) { + errMsg = qsTr("Use only lowercase letters (a to z), numbers & dashes (-). Do not use chat keys.") + } + + return errMsg + } + + /* Validation section end */ + + + // Leave this function at the bottom of the file as QT Creator messes up the code color after this function isPunct(c) { return /(!|\@|#|\$|%|\^|&|\*|\(|\)|_|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c) diff --git a/ui/shared/Input.qml b/ui/shared/Input.qml index cc1ebeb5c3..fab1a4f1e6 100644 --- a/ui/shared/Input.qml +++ b/ui/shared/Input.qml @@ -9,6 +9,7 @@ Item { property string placeholderText: "My placeholder" property string placeholderTextColor: Style.current.secondaryText property alias text: inputValue.text + property alias maxLength: inputValue.maximumLength property string validationError: "" property alias validationErrorAlignment: validationErrorText.horizontalAlignment property int validationErrorTopMargin: 1