From c40e831afc3a393763d3ddf7bb78ee65a13daf84 Mon Sep 17 00:00:00 2001 From: bill bittner <bittner.w@gmail.com> Date: Thu, 4 Jan 2018 18:34:17 -0800 Subject: [PATCH] added validation to url input --- public/assets/js/generalFunctions.js | 4 +- react/components/publishForm.jsx | 14 +++- react/components/urlChooser.jsx | 99 +++++++++++++++++++++++ react/components/urlInput.jsx | 39 --------- react/uploader.js | 74 ++++++++++++++--- views/partials/publishForm-Url.handlebars | 16 ---- 6 files changed, 173 insertions(+), 73 deletions(-) create mode 100644 react/components/urlChooser.jsx delete mode 100644 react/components/urlInput.jsx diff --git a/public/assets/js/generalFunctions.js b/public/assets/js/generalFunctions.js index ec56009f..6ba5515b 100644 --- a/public/assets/js/generalFunctions.js +++ b/public/assets/js/generalFunctions.js @@ -7,7 +7,7 @@ function getRequest (url) { if (xhttp.readyState == 4 ) { if ( xhttp.status == 200) { resolve(xhttp.response); - } else if (xhttp.status == 401) { + } else if (xhttp.status == 403) { reject('Wrong channel name or password'); } else { reject('request failed with status:' + xhttp.status); @@ -48,7 +48,7 @@ function toggleSection(event){ var slaveElement = document.getElementById(dataSet.slaveelementid); var closedLabel = dataSet.closedlabel; var openLabel = dataSet.openlabel; - + if (status === "false") { slaveElement.hidden = false; masterElement.innerText = openLabel; diff --git a/react/components/publishForm.jsx b/react/components/publishForm.jsx index 839c3c9a..93813475 100644 --- a/react/components/publishForm.jsx +++ b/react/components/publishForm.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PreviewDropzone from './previewDropzone.jsx'; import TitleInput from './titleInput.jsx'; import ChannelSelector from './channelSelector.jsx'; -import UrlInput from './urlInput.jsx'; +import UrlChooser from './urlChooser.jsx'; import ThumbnailInput from './thumbnailInput.jsx'; import MetadataInputs from './metadataInputs.jsx'; @@ -79,6 +79,16 @@ class PublishForm extends React.Component { <div className="column column--5 column--sml-10 align-content-top"> <div id="publish-active-area" className="row row--padded"> + <UrlChooser + fileName={this.props.file.name} + claim={this.props.claim} + publishToChannel={this.props.publishToChannel} + loggedInChannelName={this.props.loggedInChannelName} + loggedInChannelShortId={this.props.loggedInChannelShortId} + updateUploaderState={this.updateUploaderState} + makeGetRequest={this.props.makeGetRequest} + /> + <AnonymousOrChannelSelect publishToChannel={this.props.publishToChannel} updateUploaderState={this.props.updateUploaderState}/> <ChannelSelector @@ -89,8 +99,6 @@ class PublishForm extends React.Component { channelError={this.state.channelError} /> - <UrlInput file={this.props.file}/> - { (this.props.file.type === 'video/mp4') && <ThumbnailInput thumbnail={this.props.thumbnail}/> } <MetadataInputs /> diff --git a/react/components/urlChooser.jsx b/react/components/urlChooser.jsx new file mode 100644 index 00000000..b0cb0fee --- /dev/null +++ b/react/components/urlChooser.jsx @@ -0,0 +1,99 @@ +import React from 'react'; + +function UrlMiddle ({publishToChannel, loggedInChannelName, loggedInChannelShortId}) { + if (publishToChannel) { + if (loggedInChannelName) { + return <span id="url-channel" className="url-text--secondary">{loggedInChannelName}:{loggedInChannelShortId} /</span>; + } + return <span id="url-channel-placeholder" className="url-text--secondary tooltip">@channel<span + className="tooltip-text">Select a channel below</span> /</span>; + } + return ( + <span id="url-no-channel-placeholder" className="url-text--secondary tooltip">xyz<span className="tooltip-text">This will be a random id</span> /</span> + ); +} + +class UrlInput extends React.Component { + constructor (props) { + super(props); + this.state = { + urlError : null, + urlBeginning: 'spee.ch', + urlMiddle : null, + }; + this.handleInput = this.handleInput.bind(this); + this.validateClaimName = this.validateClaimName.bind(this); + this.cleanseClaimName = this.cleanseClaimName.bind(this); + this.checkClaimIsValidAndAvailable = this.checkClaimIsValidAndAvailable.bind(this); + } + handleInput (event) { + event.preventDefault(); + let value = event.target.value; + const name = event.target.name; + value = this.cleanseClaimName(value); + this.props.updateUploaderState(name, value); + this.checkClaimIsValidAndAvailable(value); + } + validateClaimName (claim) { + // ensure a name was entered + if (!claim || claim.length < 1) { + throw new Error('You must enter a name for your url'); + } + // validate the characters in the 'name' field + const invalidCharacters = /[^A-Za-z0-9,-]/g.exec(claim); + if (invalidCharacters) { + throw new Error('"' + invalidCharacters + '" characters are not allowed'); + } + return claim; + } + cleanseClaimName (name) { + name = name.replace(/\s+/g, '-'); // replace spaces with dashes + name = name.replace(/[^A-Za-z0-9-]/g, ''); // remove all characters that are not A-Z, a-z, 0-9, or '-' + return name; + } + checkClaimIsValidAndAvailable (claim) { + // validationFunctions.checkClaimName(event.target.value) + try { + claim = this.validateClaimName(claim); + } catch (error) { + this.setState({urlError: error.message}); + return; + } + const that = this; + this.props.makeGetRequest(`/api/claim-is-available/${claim}`) + .then(() => { + that.setState({urlError: null}); + }) + .catch((error) => { + that.setState({urlError: error.message}); + }); + } + render () { + return ( + <div> + <div className="row row--padded row--no-top row--wide"> + + <p id="input-error-claim-name" className="info-message-placeholder info-message--failure">{this.state.urlError}</p> + + <div className="column column--3 column--sml-10"> + <label className="label">URL:</label> + </div><div className="column column--7 column--sml-10 input-text--primary span--relative"> + + <span className="url-text--secondary">{this.state.urlBeginning} / </span> + + <UrlMiddle publishToChannel={this.props.publishToChannel} loggedInChannelName={this.props.loggedInChannelName} loggedInChannelShortId={this.props.loggedInChannelShortId}/> + + <input type="text" id="claim-name-input" className="input-text" name='claim' placeholder="your-url-here" onInput={this.handleInput} value={this.props.claim}/> + { (this.props.claim && !this.state.urlError) && ( + <span id="input-success-claim-name" className="info-message--success span--absolute">{'\u2713'}</span> + )} + + </div> + + </div> + </div> + ); + } +} + +module.exports = UrlInput; diff --git a/react/components/urlInput.jsx b/react/components/urlInput.jsx deleted file mode 100644 index 9105b33b..00000000 --- a/react/components/urlInput.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react'; - -class UrlInput extends React.Component { - constructor (props) { - super(props); - this.updateUrl = this.updateUrl.bind(this); - } - updateUrl (selectedOption) { - const urlChannel = document.getElementById('url-channel'); - const urlNoChannelPlaceholder = document.getElementById('url-no-channel-placeholder'); - const urlChannelPlaceholder = document.getElementById('url-channel-placeholder'); - if (selectedOption === 'new' || selectedOption === 'login' || selectedOption === ''){ - urlChannel.hidden = true; - urlNoChannelPlaceholder.hidden = true; - urlChannelPlaceholder.hidden = false; - } else if (selectedOption === 'anonymous'){ - urlChannel.hidden = true; - urlNoChannelPlaceholder.hidden = false; - urlChannelPlaceholder.hidden = true; - } else { - urlChannel.hidden = false; - // show channel and short id - const selectedChannel = getCookie('channel_name'); - const shortChannelId = getCookie('short_channel_id'); - urlChannel.innerText = `${selectedChannel}:${shortChannelId}`; - urlNoChannelPlaceholder.hidden = true; - urlChannelPlaceholder.hidden = true; - } - } - render () { - return ( - <div> - <h3>url component</h3> - </div> - ); - } -} - -module.exports = UrlInput; diff --git a/react/uploader.js b/react/uploader.js index 4c980d6e..9115f0c3 100644 --- a/react/uploader.js +++ b/react/uploader.js @@ -8,17 +8,18 @@ const DROPZONE = 'DROPZONE'; const DETAILS = 'DETAILS'; const STATUS = 'STATUS'; const initialState = { - showComponent : DROPZONE, // DROPZONE, DETAILS, or PUBLISHING - loggedInChannel : null, - publishToChannel: false, - file : null, - title : '', - channel : null, - url : '', - thumbnail : '', - description : '', - license : '', - nsfw : '', + showComponent : DROPZONE, // DROPZONE, DETAILS, or PUBLISHING + loggedInChannelName : null, + loggedInChannelShortId: null, + publishToChannel : false, + file : null, + title : '', + channel : null, + claim : '', + thumbnail : '', + description : '', + license : '', + nsfw : '', }; class Uploader extends React.Component { @@ -30,10 +31,16 @@ class Uploader extends React.Component { this.clearUploaderState = this.clearUploaderState.bind(this); this.showComponent = this.showComponent.bind(this); this.stageFileAndShowDetails = this.stageFileAndShowDetails.bind(this); + this.makeGetRequest = this.makeGetRequest.bind(this); + this.makePostRequest = this.makePostRequest.bind(this); } componentDidMount () { // check for whether a channel is logged in // if so, setState loggedInChannel to the channel name + // const loggedInChannel = getCookie('channel_name'); + // this.setState({loggedInChannel}) + // const loggedInChannelShortId = getCookie('short_channel_id'); + // this.setState({loggedInChannelShortId}) } updateUploaderState (name, value) { console.log(`updateUploaderState ${name} ${value}`); @@ -52,6 +59,45 @@ class Uploader extends React.Component { // hide the dropzone and show the details this.showComponent(DETAILS); } + makeGetRequest (url) { + return new Promise((resolve, reject) => { + let xhttp = new XMLHttpRequest(); + xhttp.open('GET', url, true); + xhttp.responseType = 'json'; + xhttp.onreadystatechange = () => { + if (xhttp.readyState == 4 ) { + if ( xhttp.status == 200) { + resolve(xhttp.response); + } else if (xhttp.status == 403) { + reject('Wrong channel name or password'); + } else { + reject('request failed with status:' + xhttp.status); + }; + } + }; + xhttp.send(); + }); + } + makePostRequest (url, params) { + return new Promise((resolve, reject) => { + let xhttp = new XMLHttpRequest(); + xhttp.open('POST', url, true); + xhttp.responseType = 'json'; + xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + xhttp.onreadystatechange = () => { + if (xhttp.readyState == 4 ) { + if ( xhttp.status == 200) { + resolve(xhttp.response); + } else if (xhttp.status == 403) { + reject('Wrong channel name or password'); + } else { + reject('request failed with status:' + xhttp.status); + }; + } + }; + xhttp.send(params); + }); + } render () { return ( <div className="row row--tall flex-container--column"> @@ -62,12 +108,14 @@ class Uploader extends React.Component { <PublishForm updateUploaderState={this.updateUploaderState} clearUploaderState={this.clearUploaderState} - loggedInChannel={this.state.loggedInChannel} + makeGetRequest={this.makeGetRequest} + loggedInChannelName={this.state.loggedInChannelName} + loggedInChannelShortId={this.state.loggedInChannelShortId} publishToChannel={this.state.publishToChannel} file={this.state.file} title={this.state.title} channel={this.state.channel} - url={this.state.url} + claim={this.state.claim} thumbnail={this.state.thumbnail} description={this.state.description} license={this.state.license} diff --git a/views/partials/publishForm-Url.handlebars b/views/partials/publishForm-Url.handlebars index d5200ae4..e69de29b 100644 --- a/views/partials/publishForm-Url.handlebars +++ b/views/partials/publishForm-Url.handlebars @@ -1,16 +0,0 @@ -<div class="row row--padded row--wide"> - <!--error display--> - <p id="input-error-claim-name" class="info-message-placeholder info-message--failure" hidden="true"></p> - <!--url selection--> - <div class="column column--3 column--sml-10"> - <label class="label">URL:</label> - </div><div class="column column--7 column--sml-10 input-text--primary span--relative"> - <span class="url-text--secondary">spee.ch /</span> - <span id="url-channel" class="url-text--secondary" {{#if user}}{{else}}hidden="true"{{/if}}>{{user.channelName}}:{{user.shortChannelId}}</span> - <span id="url-no-channel-placeholder" class="url-text--secondary tooltip" {{#if user}}hidden="true"{{else}}{{/if}}>xyz<span class="tooltip-text">This will be a random id</span></span> - <span id="url-channel-placeholder" class="url-text--secondary tooltip" hidden="true">@channel<span class="tooltip-text">Select a channel above</span></span> / - <input type="text" id="claim-name-input" class="input-text" placeholder="your-url-here" oninput="validationFunctions.checkClaimName(event.target.value)"> - <span id="input-success-claim-name" class="info-message--success span--absolute"></span> - </div> - -</div> \ No newline at end of file