Client side input #122
7 changed files with 167 additions and 109 deletions
|
@ -123,6 +123,7 @@ table {
|
||||||
.input-error {
|
.input-error {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: red;
|
color: red;
|
||||||
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1250px) {
|
@media (max-width: 1250px) {
|
||||||
|
|
|
@ -61,6 +61,12 @@ function showError(elementId, errorMsg) {
|
||||||
errorDisplay.innerText = errorMsg;
|
errorDisplay.innerText = errorMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearError(elementId,) {
|
||||||
|
var errorDisplay = document.getElementById(elementId);
|
||||||
|
errorDisplay.hidden = true;
|
||||||
|
errorDisplay.innerText = '';
|
||||||
|
}
|
||||||
|
|
||||||
// Create new error objects, that prototypically inherit from the Error constructor
|
// Create new error objects, that prototypically inherit from the Error constructor
|
||||||
function FileError(message) {
|
function FileError(message) {
|
||||||
this.name = 'FileError';
|
this.name = 'FileError';
|
||||||
|
|
|
@ -2,96 +2,6 @@
|
||||||
function updatePublishStatus(msg){
|
function updatePublishStatus(msg){
|
||||||
document.getElementById('publish-status').innerHTML = msg;
|
document.getElementById('publish-status').innerHTML = msg;
|
||||||
}
|
}
|
||||||
// validation function which checks the proposed file's type, size, and name
|
|
||||||
function validateFile(file) {
|
|
||||||
if (!file) {
|
|
||||||
throw new Error('no file provided');
|
|
||||||
}
|
|
||||||
// validate size and type
|
|
||||||
switch (file.type) {
|
|
||||||
case 'image/jpeg':
|
|
||||||
case 'image/jpg':
|
|
||||||
case 'image/png':
|
|
||||||
case 'image/gif':
|
|
||||||
if (file.size > 10000000){
|
|
||||||
throw new Error('Sorry, images are limited to 10 megabytes.');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'video/mp4':
|
|
||||||
if (file.size > 50000000){
|
|
||||||
throw new Error('Sorry, videos are limited to 50 megabytes.');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(file.type + ' is not supported a supported file type. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.')
|
|
||||||
}
|
|
||||||
// validate the file name (note: different from the lbry claim name)
|
|
||||||
var invalidCharacter = /[^\w.-\s()]/g.exec(file.name);
|
|
||||||
if (invalidCharacter) {
|
|
||||||
throw new Error('Special characters, such as "' + invalidCharacter + '", are not allowed in the file name.');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// validation function that checks to make sure the claim name is not already claimed
|
|
||||||
function validateClaimName (name) {
|
|
||||||
var deferred = new Promise(function(resolve, reject) {
|
|
||||||
// validate the characters in the 'name' field
|
|
||||||
if (name.length < 1) {
|
|
||||||
reject(new NameError("You must enter a name for your claim"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var invalidCharacters = /[^A-Za-z0-9,-]/g.exec(name);
|
|
||||||
if (invalidCharacters) {
|
|
||||||
reject(new NameError('"' + invalidCharacters + '" is not allowed. Use only the following characters: A-Z, a-z, 0-9, and "-"'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// make sure the claim name is still available
|
|
||||||
var xhttp;
|
|
||||||
xhttp = new XMLHttpRequest();
|
|
||||||
xhttp.open('GET', '/api/isClaimAvailable/' + name, true);
|
|
||||||
xhttp.responseType = 'json';
|
|
||||||
xhttp.onreadystatechange = function() {
|
|
||||||
if (this.readyState == 4 ) {
|
|
||||||
if ( this.status == 200) {
|
|
||||||
if (this.response == true) {
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
reject( new NameError("That name has already been claimed by spee.ch. Please choose a different name."));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
reject("request to check claim name failed with status:" + this.status);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.send();
|
|
||||||
});
|
|
||||||
return deferred;
|
|
||||||
}
|
|
||||||
// validation function which checks all aspects of the publish submission
|
|
||||||
function validateSubmission(stagedFiles, name){
|
|
||||||
var deferred = new Promise(function (resolve, reject) {
|
|
||||||
// make sure only 1 file was selected
|
|
||||||
if (!stagedFiles) {
|
|
||||||
reject(new FileError("Please select a file"));
|
|
||||||
} else if (stagedFiles.length > 1) {
|
|
||||||
reject(new FileError("Only one file is allowed at a time"));
|
|
||||||
}
|
|
||||||
// validate the file's name, type, and size
|
|
||||||
try {
|
|
||||||
validateFile(stagedFiles[0]);
|
|
||||||
} catch (error) {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
// make sure the claim name has not already been used
|
|
||||||
validateClaimName(name)
|
|
||||||
.then(function() {
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
reject(error);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
return deferred;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* publish helper functions */
|
/* publish helper functions */
|
||||||
|
|
||||||
|
@ -121,7 +31,8 @@ function previewAndStageFile(selectedFile){
|
||||||
// set the name input value to the image name if none is set yet
|
// set the name input value to the image name if none is set yet
|
||||||
if (nameInput.value === "") {
|
if (nameInput.value === "") {
|
||||||
var filename = selectedFile.name.substring(0, selectedFile.name.indexOf('.'))
|
var filename = selectedFile.name.substring(0, selectedFile.name.indexOf('.'))
|
||||||
nameInput.value = filename.replace(/\s+/g, '-');;
|
nameInput.value = cleanseClaimName(filename);
|
||||||
|
checkClaimName(nameInput.value);
|
||||||
}
|
}
|
||||||
// store the selected file for upload
|
// store the selected file for upload
|
||||||
stagedFiles = [selectedFile];
|
stagedFiles = [selectedFile];
|
||||||
|
|
123
public/assets/js/validationFunctions.js
Normal file
123
public/assets/js/validationFunctions.js
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
|
||||||
|
|
||||||
|
// validation function which checks the proposed file's type, size, and name
|
||||||
|
function validateFile(file) {
|
||||||
|
if (!file) {
|
||||||
|
throw new Error('no file provided');
|
||||||
|
}
|
||||||
|
if (/'/.test(file.name)) {
|
||||||
|
throw new Error('apostrophes are not allowed in the file name');
|
||||||
|
}
|
||||||
|
// validate size and type
|
||||||
|
switch (file.type) {
|
||||||
|
case 'image/jpeg':
|
||||||
|
case 'image/jpg':
|
||||||
|
case 'image/png':
|
||||||
|
case 'image/gif':
|
||||||
|
if (file.size > 10000000){
|
||||||
|
throw new Error('Sorry, images are limited to 10 megabytes.');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'video/mp4':
|
||||||
|
if (file.size > 50000000){
|
||||||
|
throw new Error('Sorry, videos are limited to 50 megabytes.');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(file.type + ' is not a supported file type. Only, .jpeg, .png, .gif, and .mp4 files are currently supported.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// validation function that checks to make sure the claim name is not already claimed
|
||||||
|
function isNameAvailable (name) {
|
||||||
|
var deferred = new Promise(function(resolve, reject) {
|
||||||
|
// make sure the claim name is still available
|
||||||
|
var xhttp;
|
||||||
|
xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.open('GET', '/api/isClaimAvailable/' + name, true);
|
||||||
|
xhttp.responseType = 'json';
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if (this.readyState == 4 ) {
|
||||||
|
if ( this.status == 200) {
|
||||||
|
if (this.response == true) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject( new NameError("That name has already been claimed by another user. Please choose a different name."));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reject("request to check claim name failed with status:" + this.status);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhttp.send();
|
||||||
|
});
|
||||||
|
return deferred;
|
||||||
|
}
|
||||||
|
// validation function that checks to make sure the claim name is valid
|
||||||
|
function validateClaimName (name) {
|
||||||
|
// ensure a name was entered
|
||||||
|
if (name.length < 1) {
|
||||||
|
throw new NameError("You must enter a name for your claim");
|
||||||
|
}
|
||||||
|
// validate the characters in the 'name' field
|
||||||
|
const invalidCharacters = /[^A-Za-z0-9,-]/g.exec(name);
|
||||||
|
if (invalidCharacters) {
|
||||||
|
throw new NameError('"' + invalidCharacters + '" characters are not allowed in the title.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function 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;
|
||||||
|
}
|
||||||
|
// validaiton function to check claim name as the input changes
|
||||||
|
function checkClaimName(name){
|
||||||
|
try {
|
||||||
|
// check to make sure the characters are valid
|
||||||
|
validateClaimName(name);
|
||||||
|
clearError('input-error-claim-name');
|
||||||
|
// check to make sure it is availabe
|
||||||
|
isNameAvailable(name)
|
||||||
|
.then(function() {
|
||||||
|
document.getElementById('claim-name-available').hidden = false;
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
document.getElementById('claim-name-available').hidden = true;
|
||||||
|
showError('input-error-claim-name', error.message);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
showError('input-error-claim-name', error.message);
|
||||||
|
document.getElementById('claim-name-available').hidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// validation function which checks all aspects of the publish submission
|
||||||
|
function validateSubmission(stagedFiles, name){
|
||||||
|
var deferred = new Promise(function (resolve, reject) {
|
||||||
|
// make sure only 1 file was selected
|
||||||
|
if (!stagedFiles) {
|
||||||
|
reject(new FileError("Please select a file"));
|
||||||
|
} else if (stagedFiles.length > 1) {
|
||||||
|
reject(new FileError("Only one file is allowed at a time"));
|
||||||
|
}
|
||||||
|
// validate the file's name, type, and size
|
||||||
|
try {
|
||||||
|
validateFile(stagedFiles[0]);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
// make sure the claim name has not already been used
|
||||||
|
try {
|
||||||
|
validateClaimName(name);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
isNameAvailable(name)
|
||||||
|
.then(function() {
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return deferred;
|
||||||
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
<script src="/socket.io/socket.io.js"></script>
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
<script src="/siofu/client.js"></script>
|
<script src="/siofu/client.js"></script>
|
||||||
<script src="/assets/js/generalFunctions.js"></script>
|
<script src="/assets/js/generalFunctions.js"></script>
|
||||||
|
<script src="/assets/js/validationFunctions.js"></script>
|
||||||
<script src="/assets/js/publishFunctions.js"></script>
|
<script src="/assets/js/publishFunctions.js"></script>
|
||||||
<script typ="text/javascript">
|
<script typ="text/javascript">
|
||||||
// define variables
|
// define variables
|
||||||
|
@ -59,9 +60,4 @@
|
||||||
window.location.href = showUrl;
|
window.location.href = showUrl;
|
||||||
});
|
});
|
||||||
|
|
||||||
//event listener to filter claim name inputs
|
|
||||||
document.getElementById('claim-name-input').addEventListener('input', function() {
|
|
||||||
var name = this.value;
|
|
||||||
this.value = name.replace(/\s+/g, '-');
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,21 +1,19 @@
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<h2>Publish</h2>
|
<h2>Publish</h2>
|
||||||
<div class="col-left">
|
<div class="col-left" id="file-selection-area">
|
||||||
<div id="drop-zone" ondrop="drop_handler(event);" ondragover="dragover_handler(event);" ondragend="dragend_handler(event)">
|
<div id="drop-zone" ondrop="drop_handler(event);" ondragover="dragover_handler(event);" ondragend="dragend_handler(event)">
|
||||||
<p>Drag and drop your file here, or choose your file below.</p>
|
<p>Drag and drop your file here, or choose your file below.</p>
|
||||||
<div class="input-error" id="input-error-file-selection" hidden="true"></div>
|
<div class="input-error" id="input-error-file-selection" hidden="true"></div>
|
||||||
<input type="file" id="siofu_input" name="file" accept="video/*,image/*" onchange="previewAndStageFile(event.target.files[0])" enctype="multipart/form-data"/>
|
<input type="file" id="siofu_input" name="file" accept="video/*,image/*" onchange="previewAndStageFile(event.target.files[0])" enctype="multipart/form-data"/>
|
||||||
</div>
|
</div>
|
||||||
<div id="asset-preview-holder">
|
<div id="asset-preview-holder"></div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-right">
|
<div class="col-right">
|
||||||
<textarea id="direct-link-holder" hidden="true">No URL yet</textarea>
|
|
||||||
<div id="publish-active-area">
|
<div id="publish-active-area">
|
||||||
<div class="input-error" id="input-error-claim-name" hidden="true"></div>
|
<div class="input-error" id="input-error-claim-name" hidden="true"></div>
|
||||||
<div class="claim-name-input-area">
|
<div class="claim-name-input-area">
|
||||||
Spee.ch/<input type="text" id="claim-name-input" placeholder="your-name-here" class="form-control">
|
Spee.ch/<input type="text" id="claim-name-input" placeholder="your-title-here" oninput="checkClaimName(event.target.value)">
|
||||||
|
<span id="claim-name-available" hidden="true" style="color: green">✔</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="stop-float">
|
<p class="stop-float">
|
||||||
<label for="publish-license">License:</label>
|
<label for="publish-license">License:</label>
|
||||||
|
@ -30,9 +28,33 @@
|
||||||
<p>
|
<p>
|
||||||
<div class="input-error" id="input-error-publish-submit" hidden="true"></div>
|
<div class="input-error" id="input-error-publish-submit" hidden="true"></div>
|
||||||
<button id="publish-submit" onclick="publishSelectedImage(event)">Publish</button>
|
<button id="publish-submit" onclick="publishSelectedImage(event)">Publish</button>
|
||||||
<a href="/"><button id="publish-reset">Reset</button></a>
|
<button onclick="resetPublishArea()">Reset</button>
|
||||||
</p>
|
</p>
|
||||||
<p><i>By clicking 'Publish' I attest that I have read and agree to the <a href="https://lbry.io/termsofservice" target="_blank">LBRY terms of service</a>.</i></p>
|
<p><i>By clicking 'Publish' I attest that I have read and agree to the <a href="https://lbry.io/termsofservice" target="_blank">LBRY terms of service</a>.</i></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script type="text/javascript" >
|
||||||
|
function resetPublishArea (){
|
||||||
|
// reset file selection area
|
||||||
|
document.getElementById('file-selection-area').innerHTML = `<div id="drop-zone" ondrop="drop_handler(event);" ondragover="dragover_handler(event);" ondragend="dragend_handler(event)">
|
||||||
|
<p>Drag and drop your file here, or choose your file below.</p>
|
||||||
|
<div class="input-error" id="input-error-file-selection" hidden="true"></div>
|
||||||
|
<input type="file" id="siofu_input" name="file" accept="video/*,image/*" onchange="previewAndStageFile(event.target.files[0])" enctype="multipart/form-data"/>
|
||||||
|
</div>
|
||||||
|
<div id="asset-preview-holder"></div>`;
|
||||||
|
// reset
|
||||||
|
document.getElementById('claim-name-input').value = '';
|
||||||
|
// remove staged files
|
||||||
|
stagedFiles = null;
|
||||||
|
// clear any errors
|
||||||
|
document.getElementById('input-error-file-selection').innerHTML = '';
|
||||||
|
document.getElementById('input-error-claim-name').innerHTML = '';
|
||||||
|
document.getElementById('input-error-publish-submit').innerHTML = '';
|
||||||
|
// remove nsfw check
|
||||||
|
document.getElementById('claim-name-available').hidden = true;
|
||||||
|
// remove nsfw check
|
||||||
|
document.getElementById('publish-nsfw').checked = false;
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -7,13 +7,13 @@
|
||||||
{{#unless this.nsfw}}
|
{{#unless this.nsfw}}
|
||||||
<a href="/{{this.name}}/{{this.claimId}}">
|
<a href="/{{this.name}}/{{this.claimId}}">
|
||||||
{{#ifConditional this.fileType '===' 'video/mp4'}}
|
{{#ifConditional this.fileType '===' 'video/mp4'}}
|
||||||
<video class="grid-item trending-video" controls onloadeddata="resetLayout()">
|
<video class="grid-item trending-video" controls onloadeddata="resetTrendingLayout()">
|
||||||
<source src="/media/{{this.fileName}}" >
|
<source src="/media/{{this.fileName}}" >
|
||||||
{{!--fallback--}}
|
{{!--fallback--}}
|
||||||
Your browser does not support the <code>video</code> element.
|
Your browser does not support the <code>video</code> element.
|
||||||
</video>
|
</video>
|
||||||
{{else}}
|
{{else}}
|
||||||
<img class="grid-item trending-image" src="/media/{{this.fileName}}" onload="resetLayout()"/>
|
<img class="grid-item trending-image" src="/media/{{this.fileName}}" onload="resetTrendingLayout()"/>
|
||||||
{{/ifConditional}}
|
{{/ifConditional}}
|
||||||
</a>
|
</a>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
@ -29,8 +29,7 @@
|
||||||
itemSelector: '.grid-item'
|
itemSelector: '.grid-item'
|
||||||
});
|
});
|
||||||
|
|
||||||
function resetLayout() {
|
function resetTrendingLayout() {
|
||||||
console.log('resetting layout');
|
|
||||||
msnry.layout();
|
msnry.layout();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in a new issue