Channel configuration UI: form validation.

This commit is contained in:
John Livingston 2023-09-25 12:51:15 +02:00
parent e2c85af001
commit 06b9417650
No known key found for this signature in database
GPG Key ID: B17B5640CE66CDBC
4 changed files with 85 additions and 7 deletions

View File

@ -69,3 +69,5 @@ declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_QUOTE_DELAY_DESC: string
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_BANNED_JIDS_LABEL: string declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_BANNED_JIDS_LABEL: string
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_BOT_NICKNAME: string declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_BOT_NICKNAME: string
declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FOR_MORE_INFO: string declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_FOR_MORE_INFO: string
declare const LOC_INVALID_VALUE: string

View File

@ -132,8 +132,9 @@ async function vivifyConfigurationChannel (
): Promise<void> { ): Promise<void> {
const form = rootEl.querySelector('form[livechat-configuration-channel-options]') as HTMLFormElement const form = rootEl.querySelector('form[livechat-configuration-channel-options]') as HTMLFormElement
if (!form) { return } if (!form) { return }
const labelSaved = await clientOptions.peertubeHelpers.translate(LOC_SUCCESSFULLY_SAVED) const translate = clientOptions.peertubeHelpers.translate
const labelError = await clientOptions.peertubeHelpers.translate(LOC_ERROR) const labelSaved = await translate(LOC_SUCCESSFULLY_SAVED)
const labelError = await translate(LOC_ERROR)
const enableBotCB = form.querySelector('input[name=bot]') as HTMLInputElement const enableBotCB = form.querySelector('input[name=bot]') as HTMLInputElement
const botEnabledEl = form.querySelectorAll('[livechat-configuration-channel-options-bot-enabled]') const botEnabledEl = form.querySelectorAll('[livechat-configuration-channel-options-bot-enabled]')
@ -147,8 +148,70 @@ async function vivifyConfigurationChannel (
}) })
} }
const removeDisplayedErrors = (): void => {
form.querySelectorAll('.form-error').forEach(el => el.remove())
}
const displayError = async (fieldSelector: string, message: string): Promise<void> => {
form.querySelectorAll(fieldSelector).forEach(el => {
const erEl = document.createElement('div')
erEl.classList.add('form-error')
erEl.textContent = message
el.after(erEl)
})
}
const validateData: Function = async (channelConfigurationOptions: ChannelConfigurationOptions): Promise<boolean> => {
const botConf = channelConfigurationOptions.bot
const errorFieldSelectors = []
if (/[^\p{L}\p{N}\p{Z}_-]/u.test(botConf.nickname ?? '')) {
const selector = '#peertube-livechat-bot-nickname'
errorFieldSelectors.push(selector)
await displayError(selector, await translate(LOC_INVALID_VALUE))
}
for (let iFw = 0; iFw < botConf.forbiddenWords.length; iFw++) {
const fw = botConf.forbiddenWords[iFw]
if (fw.regexp) {
for (const v of fw.entries) {
if (v === '' || /^\s+$/.test(v)) { continue }
try {
// eslint-disable-next-line no-new
new RegExp(v)
} catch (err) {
const selector = '#peertube-livechat-forbidden-words-' + iFw.toString()
errorFieldSelectors.push(selector)
let message = await translate(LOC_INVALID_VALUE)
message += ` "${v}": ${err as string}`
await displayError(selector, message)
}
}
}
}
for (let iCd = 0; iCd < botConf.commands.length; iCd++) {
const cd = botConf.commands[iCd]
if (/\s+/.test(cd.command)) {
const selector = '#peertube-livechat-command-' + iCd.toString()
errorFieldSelectors.push(selector)
const message = await translate(LOC_INVALID_VALUE)
await displayError(selector, message)
}
}
if (errorFieldSelectors.length) {
// Set the focus to the first in-error field:
const el: HTMLInputElement | HTMLTextAreaElement | null = document.querySelector(errorFieldSelectors[0])
el?.focus()
return false
}
return true
}
const submitForm: Function = async () => { const submitForm: Function = async () => {
const data = new FormData(form) const data = new FormData(form)
removeDisplayedErrors()
const channelConfigurationOptions: ChannelConfigurationOptions = { const channelConfigurationOptions: ChannelConfigurationOptions = {
bot: { bot: {
enabled: data.get('bot') === '1', enabled: data.get('bot') === '1',
@ -160,8 +223,7 @@ async function vivifyConfigurationChannel (
} }
} }
// TODO: handle form errors. // Note: but data in order, because validateData assume index are okay to find associated fields.
for (let i = 0; data.has('forbidden_words_' + i.toString()); i++) { for (let i = 0; data.has('forbidden_words_' + i.toString()); i++) {
const entries = (data.get('forbidden_words_' + i.toString())?.toString() ?? '') const entries = (data.get('forbidden_words_' + i.toString())?.toString() ?? '')
.split(/\r?\n|\r|\n/g) .split(/\r?\n|\r|\n/g)
@ -180,6 +242,7 @@ async function vivifyConfigurationChannel (
channelConfigurationOptions.bot.forbiddenWords.push(fw) channelConfigurationOptions.bot.forbiddenWords.push(fw)
} }
// Note: but data in order, because validateData assume index are okay to find associated fields.
for (let i = 0; data.has('quote_' + i.toString()); i++) { for (let i = 0; data.has('quote_' + i.toString()); i++) {
const messages = (data.get('quote_' + i.toString())?.toString() ?? '') const messages = (data.get('quote_' + i.toString())?.toString() ?? '')
.split(/\r?\n|\r|\n/g) .split(/\r?\n|\r|\n/g)
@ -196,6 +259,7 @@ async function vivifyConfigurationChannel (
channelConfigurationOptions.bot.quotes.push(q) channelConfigurationOptions.bot.quotes.push(q)
} }
// Note: but data in order, because validateData assume index are okay to find associated fields.
for (let i = 0; data.has('command_' + i.toString()); i++) { for (let i = 0; data.has('command_' + i.toString()); i++) {
const command = (data.get('command_' + i.toString())?.toString() ?? '') const command = (data.get('command_' + i.toString())?.toString() ?? '')
const message = (data.get('command_message_' + i.toString())?.toString() ?? '') const message = (data.get('command_message_' + i.toString())?.toString() ?? '')
@ -206,6 +270,10 @@ async function vivifyConfigurationChannel (
channelConfigurationOptions.bot.commands.push(c) channelConfigurationOptions.bot.commands.push(c)
} }
if (!await validateData(channelConfigurationOptions)) {
throw new Error('Invalid form data')
}
const headers: any = clientOptions.peertubeHelpers.getAuthHeader() ?? {} const headers: any = clientOptions.peertubeHelpers.getAuthHeader() ?? {}
headers['content-type'] = 'application/json;charset=UTF-8' headers['content-type'] = 'application/json;charset=UTF-8'
@ -235,6 +303,9 @@ async function vivifyConfigurationChannel (
enableBotCB.onclick = () => refresh() enableBotCB.onclick = () => refresh()
form.onsubmit = () => { form.onsubmit = () => {
toggleSubmit(true) toggleSubmit(true)
if (!form.checkValidity()) {
return false
}
submitForm().then( submitForm().then(
() => { () => {
clientOptions.peertubeHelpers.notifier.success(labelSaved) clientOptions.peertubeHelpers.notifier.success(labelSaved)

View File

@ -360,4 +360,6 @@ livechat_configuration_channel_command_message_desc: |
livechat_configuration_channel_for_more_info: | livechat_configuration_channel_for_more_info: |
For more information about how to configure this feature, please refer to the documentation by clicking on the help button. For more information about how to configure this feature, please refer to the documentation by clicking on the help button.
livechat_configuration_channel_banned_jids_label: "Banned users and patterns" livechat_configuration_channel_banned_jids_label: "Banned users and patterns"
livechat_configuration_channel_bot_nickname: "Bot nickname" livechat_configuration_channel_bot_nickname: "Bot nickname"
invalid_value: "Invalid value."

View File

@ -64,7 +64,7 @@ function _readInteger (data: any, f: string, min: number, max: number): number {
return v return v
} }
function _readSimpleInput (data: any, f: string, strict?: boolean): string { function _readSimpleInput (data: any, f: string, strict?: boolean, noSpace?: boolean): string {
if (!(f in data)) { if (!(f in data)) {
return '' return ''
} }
@ -78,6 +78,9 @@ function _readSimpleInput (data: any, f: string, strict?: boolean): string {
// Replacing all invalid characters, no need to throw an error.. // Replacing all invalid characters, no need to throw an error..
s = s.replace(/[^\p{L}\p{N}\p{Z}_-]$/gu, '') s = s.replace(/[^\p{L}\p{N}\p{Z}_-]$/gu, '')
} }
if (noSpace) {
s = s.replace(/\s+/g, '')
}
return s return s
} }
@ -185,7 +188,7 @@ function _readCommands (botData: any): ChannelConfigurationOptions['bot']['comma
const result: ChannelConfigurationOptions['bot']['commands'] = [] const result: ChannelConfigurationOptions['bot']['commands'] = []
for (const cs of botData.commands) { for (const cs of botData.commands) {
const message = _readSimpleInput(cs, 'message') const message = _readSimpleInput(cs, 'message')
const command = _readSimpleInput(cs, 'command') const command = _readSimpleInput(cs, 'command', false, true)
result.push({ result.push({
message, message,