fix(communities): Naming validation for communities

Channel, channels category name and community name handled according to the new validation.

Fixes: #2297
This commit is contained in:
Sale Djenic 2021-06-29 14:47:28 +02:00 committed by Iuri Matias
parent 341d3ebcf7
commit b9ff88f41c
5 changed files with 151 additions and 149 deletions

View File

@ -13,29 +13,29 @@ ModalPopup {
property var channels: [] property var channels: []
property bool isEdit: false 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 id: popup
height: 453 height: 453
onOpened: { onOpened: {
nameInput.text = isEdit ? categoryName : "";
if(isEdit){ if(isEdit){
nameInput.text = categoryName
channels = JSON.parse(chatsModel.communities.activeCommunity.getChatIdsByCategory(categoryId)) channels = JSON.parse(chatsModel.communities.activeCommunity.getChatIdsByCategory(categoryId))
} }
nameInput.forceActiveFocus(Qt.MouseFocusReason) nameInput.forceActiveFocus(Qt.MouseFocusReason)
if(isEdit){
validate();
}
} }
onClosed: destroy() onClosed: destroy()
function validate() { function isFormValid() {
nameInput.validate() return Utils.validateAndReturnError(nameInput.text,
return isValid categoryNameValidator,
qsTr("category name"),
maxCategoryNameLength) === ""
} }
title: isEdit ? title: isEdit ?
@ -65,25 +65,15 @@ ModalPopup {
Input { Input {
id: nameInput id: nameInput
placeholderText: qsTr("Category title") placeholderText: qsTr("Category title")
validationError: popup.nameValidationError maxLength: maxCategoryNameLength
property bool isValid: false
onTextEdited: { onTextEdited: {
validate() text = Utils.convertSpacesToDashesAndUpperToLowerCase(text);
}
function validate() { validationError = Utils.validateAndReturnError(text,
validationError = "" categoryNameValidator,
if (nameInput.text === "") { qsTr("category name"),
//% "You need to enter a name" maxCategoryNameLength)
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
} }
} }
@ -213,13 +203,13 @@ ModalPopup {
} }
footer: StatusButton { footer: StatusButton {
enabled: popup.isValid enabled: isFormValid()
text: isEdit ? text: isEdit ?
qsTr("Save") : qsTr("Save") :
qsTr("Create") qsTr("Create")
anchors.right: parent.right anchors.right: parent.right
onClicked: { onClicked: {
if (!validate()) { if (!isFormValid()) {
scrollView.scrollBackUp() scrollView.scrollBackUp()
return return
} }

View File

@ -12,15 +12,18 @@ ModalPopup {
property QtObject channel property QtObject channel
property bool isEdit: false property bool isEdit: false
property string categoryId: "" 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 Component pinnedMessagesPopupComponent
property bool isValid:
nameInput.isValid &&
descriptionTextArea.isValid
id: popup id: popup
height: 475 height: 475
@ -37,10 +40,15 @@ ModalPopup {
} }
onClosed: destroy() onClosed: destroy()
function validate() { function isFormValid() {
nameInput.validate() return Utils.validateAndReturnError(nameInput.text,
descriptionTextArea.validate() channelNameValidator,
return isValid qsTr("channel name"),
maxChannelNameLength) === ""
&& Utils.validateAndReturnError(descriptionTextArea.text,
channelDescValidator,
qsTr("channel decription"),
maxChannelDescLength) === ""
} }
//% "New channel" //% "New channel"
@ -70,30 +78,15 @@ ModalPopup {
id: nameInput id: nameInput
//% "A cool name" //% "A cool name"
placeholderText: qsTrId("a-cool-name") placeholderText: qsTrId("a-cool-name")
validationError: popup.nameValidationError maxLength: popup.maxChannelNameLength
property bool isValid: false || isEdit
onTextEdited: { onTextEdited: {
if (text.includes(" ")) { text = Utils.convertSpacesToDashesAndUpperToLowerCase(text);
text = text.replace(" ", "-")
}
validate()
}
function validate() { validationError = Utils.validateAndReturnError(text,
validationError = "" channelNameValidator,
if (nameInput.text === "") { qsTr("channel name"),
//% "You need to enter a name" maxChannelNameLength)
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
} }
} }
@ -103,36 +96,28 @@ ModalPopup {
label: qsTrId("channel-description") label: qsTrId("channel-description")
//% "What your channel is about" //% "What your channel is about"
placeholderText: qsTrId("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.top: nameInput.bottom
anchors.topMargin: Style.current.bigPadding anchors.topMargin: Style.current.bigPadding
customHeight: 88 customHeight: 88
property bool isValid: false || isEdit onTextChanged: {
onTextChanged: validate() if(text.length > maxChannelDescLength)
{
function resetValidation() { textField.remove(maxChannelDescLength, text.length)
isValid = false return
validationError = ""
} }
function validate() { validationError = Utils.validateAndReturnError(text,
validationError = "" channelDescValidator,
if (text.length > popup.maxDescChars) { qsTr("channel decription"),
validationError = qsTrId("the-description-cannot-exceed-140-characters") maxChannelDescLength)
}
if (text === "") {
validationError = qsTr("You need to enter a description")
}
isValid = validationError === ""
} }
} }
StyledText { StyledText {
id: charLimit id: charLimit
text: `${descriptionTextArea.text.length}/${maxDescChars}` text: `${descriptionTextArea.text.length}/${maxChannelDescLength}`
anchors.top: descriptionTextArea.bottom anchors.top: descriptionTextArea.bottom
anchors.topMargin: !descriptionTextArea.validationError ? 5 : - Style.current.smallPadding anchors.topMargin: !descriptionTextArea.validationError ? 5 : - Style.current.smallPadding
anchors.right: descriptionTextArea.right anchors.right: descriptionTextArea.right
@ -223,14 +208,14 @@ ModalPopup {
} }
footer: StatusButton { footer: StatusButton {
enabled: popup.isValid enabled: isFormValid()
text: isEdit ? text: isEdit ?
qsTr("Save") : qsTr("Save") :
//% "Create" //% "Create"
qsTrId("create") qsTrId("create")
anchors.right: parent.right anchors.right: parent.right
onClicked: { onClicked: {
if (!validate()) { if (!isFormValid()) {
scrollView.scrollBackUp() scrollView.scrollBackUp()
return return
} }

View File

@ -7,15 +7,20 @@ import "../../../../shared"
import "../../../../shared/status" import "../../../../shared/status"
ModalPopup { ModalPopup {
readonly property int maxDescChars: 140
property QtObject community: chatsModel.communities.activeCommunity property QtObject community: chatsModel.communities.activeCommunity
property bool isEdit: false property bool isEdit: false
property bool isValid:
nameInput.isValid && readonly property int maxCommunityNameLength: 30
descriptionTextArea.isValid && readonly property var communityNameValidator: Utils.Validate.NoEmpty
colorPicker.isValid | 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 id: popup
height: 600 height: 600
@ -34,11 +39,17 @@ ModalPopup {
} }
onClosed: destroy() onClosed: destroy()
function validate() { function isFormValid() {
nameInput.validate() return Utils.validateAndReturnError(nameInput.text,
descriptionTextArea.validate() communityNameValidator,
colorPicker.validate() qsTr("community name"),
return isValid maxCommunityNameLength) === ""
&& Utils.validateAndReturnError(descriptionTextArea.text,
communityDescValidator,
qsTr("community decription"),
maxCommunityDescLength) === ""
&& Utils.validateAndReturnError(colorPicker.text,
communityColorValidator) === ""
} }
title: isEdit ? title: isEdit ?
@ -78,28 +89,13 @@ ModalPopup {
label: qsTrId("name-your-community") label: qsTrId("name-your-community")
//% "A catchy name" //% "A catchy name"
placeholderText: qsTrId("name-your-community-placeholder") placeholderText: qsTrId("name-your-community-placeholder")
property bool isValid: false maxLength: maxCommunityNameLength
onTextEdited: { onTextEdited: {
if (text.includes(" ")) { validationError = Utils.validateAndReturnError(text,
text = text.replace(" ", "-") communityNameValidator,
} qsTr("community name"),
validate() maxCommunityNameLength)
}
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
} }
} }
@ -109,37 +105,29 @@ ModalPopup {
label: qsTrId("give-a-short-description-community") label: qsTrId("give-a-short-description-community")
//% "What your community is about" //% "What your community is about"
placeholderText: qsTrId("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.top: nameInput.bottom
anchors.topMargin: Style.current.bigPadding anchors.topMargin: Style.current.bigPadding
customHeight: 88 customHeight: 88
textField.wrapMode: TextEdit.Wrap textField.wrapMode: TextEdit.Wrap
property bool isValid: false onTextChanged: {
onTextChanged: validate() if(text.length > maxCommunityDescLength)
{
function resetValidation() { textField.remove(maxCommunityDescLength, text.length)
isValid = false return
validationError = ""
} }
function validate() { validationError = Utils.validateAndReturnError(text,
validationError = "" communityDescValidator,
if (text.length > popup.maxDescChars) { qsTr("community decription"),
validationError = qsTrId("the-description-cannot-exceed-140-characters") maxCommunityDescLength)
}
if (text === "") {
validationError = qsTr("You need to enter a description")
}
isValid = validationError === ""
} }
} }
StyledText { StyledText {
id: charLimit id: charLimit
text: `${descriptionTextArea.text.length}/${popup.maxDescChars}` text: `${descriptionTextArea.text.length}/${maxCommunityDescLength}`
anchors.top: descriptionTextArea.bottom anchors.top: descriptionTextArea.bottom
anchors.topMargin: !descriptionTextArea.validationError ? 5 : - Style.current.smallPadding anchors.topMargin: !descriptionTextArea.validationError ? 5 : - Style.current.smallPadding
anchors.right: descriptionTextArea.right anchors.right: descriptionTextArea.right
@ -285,7 +273,6 @@ ModalPopup {
Input { Input {
property string defaultColor: Style.current.blue property string defaultColor: Style.current.blue
property bool isValid: true
id: colorPicker id: colorPicker
label: qsTr("Community color") label: qsTr("Community color")
@ -295,21 +282,8 @@ ModalPopup {
textField.text: defaultColor textField.text: defaultColor
textField.onReleased: colorDialog.open() textField.onReleased: colorDialog.open()
onTextChanged: validate() onTextChanged: {
validationError = Utils.validateAndReturnError(text, communityColorValidator)
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 === ""
} }
StatusIconButton { StatusIconButton {
@ -447,6 +421,7 @@ ModalPopup {
} }
StatusButton { StatusButton {
id: btnCreateEdit id: btnCreateEdit
enabled: isFormValid()
text: isEdit ? text: isEdit ?
//% "Save" //% "Save"
qsTrId("Save") : qsTrId("Save") :
@ -454,7 +429,7 @@ ModalPopup {
qsTrId("create") qsTrId("create")
anchors.right: parent.right anchors.right: parent.right
onClicked: { onClicked: {
if (!validate()) { if (!isFormValid()) {
scrollView.scrollBackUp() scrollView.scrollBackUp()
return return
} }

View File

@ -556,6 +556,57 @@ QtObject {
return Array.from(new Set(array)) 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 // Leave this function at the bottom of the file as QT Creator messes up the code color after this
function isPunct(c) { function isPunct(c) {
return /(!|\@|#|\$|%|\^|&|\*|\(|\)|_|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c) return /(!|\@|#|\$|%|\^|&|\*|\(|\)|_|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c)

View File

@ -9,6 +9,7 @@ Item {
property string placeholderText: "My placeholder" property string placeholderText: "My placeholder"
property string placeholderTextColor: Style.current.secondaryText property string placeholderTextColor: Style.current.secondaryText
property alias text: inputValue.text property alias text: inputValue.text
property alias maxLength: inputValue.maximumLength
property string validationError: "" property string validationError: ""
property alias validationErrorAlignment: validationErrorText.horizontalAlignment property alias validationErrorAlignment: validationErrorText.horizontalAlignment
property int validationErrorTopMargin: 1 property int validationErrorTopMargin: 1